changeset 2208:b36f7b6eec39 dicom-sr

integration mainline->dicom-sr
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 22 Apr 2025 14:29:53 +0200 (6 weeks ago)
parents 02cee9f4c147 (current diff) dcfabb36dc21 (diff)
children 9b82533263f1
files Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp
diffstat 8 files changed, 535 insertions(+), 367 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Sdl/CMakeLists.txt	Tue Apr 22 14:03:14 2025 +0200
+++ b/Applications/Samples/Sdl/CMakeLists.txt	Tue Apr 22 14:29:53 2025 +0200
@@ -90,12 +90,8 @@
 
 add_executable(RtViewerSdl
   RtViewer/RtViewerSdl.cpp
-  SdlHelpers.h
   ../Common/RtViewerApp.cpp
-  ../Common/RtViewerApp.h
   ../Common/RtViewerView.cpp
-  ../Common/RtViewerView.h
-  ../Common/SampleHelpers.h
   )
 
 if (COMMAND DefineSourceBasenameForTarget)
@@ -108,9 +104,6 @@
 project(SdlSimpleViewer)
 
 add_executable(SdlSimpleViewer
-  SdlHelpers.h
-  ../Common/SampleHelpers.h
-  SingleFrameViewer/SdlSimpleViewerApplication.h
   SingleFrameViewer/SdlSimpleViewer.cpp
   )
 
--- a/Applications/Samples/WebAssembly/CMakeLists.txt	Tue Apr 22 14:03:14 2025 +0200
+++ b/Applications/Samples/WebAssembly/CMakeLists.txt	Tue Apr 22 14:29:53 2025 +0200
@@ -104,9 +104,7 @@
 add_executable(RtViewerWasm
   RtViewer/RtViewerWasm.cpp
   ../Common/RtViewerApp.cpp
-  ../Common/RtViewerApp.h
   ../Common/RtViewerView.cpp
-  ../Common/RtViewerView.h
   )
 
 if (COMMAND DefineSourceBasenameForTarget)
--- a/Applications/StoneWebViewer/WebApplication/app.js	Tue Apr 22 14:03:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebApplication/app.js	Tue Apr 22 14:29:53 2025 +0200
@@ -1778,6 +1778,23 @@
 document.onselectstart = new Function ('return false');
 
 
+
+function CallLoadOsirixAnnotations(xml, clear) {
+  /**
+   * XML files from OsiriX can be very large, so it is not possible to
+   * send them to WebAssembly as a "string" parameter, otherwise it
+   * may result in a stack overflow. We thus have to copy the
+   * JavaScript string into the WebAssembly heap.
+   * https://thewasmfrontier.hashnode.dev/working-with-string-in-webassembly
+   **/
+  var length = Module.lengthBytesUTF8(xml);
+  var pointer = stone.Allocate(length + 1);
+  Module.stringToUTF8(xml, pointer, length + 1);
+  stone.LoadOsiriXAnnotations(pointer, length + 1, clear);
+  stone.Deallocate(pointer);
+}
+
+
 window.addEventListener('message', function(e) {
   if ('type' in e.data) {
     if (e.data.type == 'show-osirix-annotations') {
@@ -1797,12 +1814,21 @@
         if ('clear' in e.data) {
           clear = e.data.clear;
         }
-        
-        app.LoadOsiriXAnnotations(e.data.xml, clear);
+
+        if ('xml' in e.data) {
+          CallLoadOsirixAnnotations(e.data.xml, clear);
+        } else if ('url' in e.data) {
+          axios.get(e.data.url)
+            .then(function(response) {
+              CallLoadOsirixAnnotations(response.data, clear);
+            });
+        } else {
+          console.error('Neither "xml", nor "url" was provided to load OsiriX annotations from');
+        }
       }
     }
     else {
-      console.log('Unknown type of dynamic action in the Stone Web viewer: ' + e.data.type);
+      console.error('Unknown type of dynamic action in the Stone Web viewer: ' + e.data.type);
     }
   }
 });
--- a/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Tue Apr 22 14:03:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Tue Apr 22 14:29:53 2025 +0200
@@ -36,7 +36,7 @@
   set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
 endif()
 
-set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\", \"lengthBytesUTF8\", \"stringToUTF8\"]'")
 set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
 set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
 set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/IStoneWebViewerContext.h	Tue Apr 22 14:29:53 2025 +0200
@@ -0,0 +1,106 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, 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 "../../../OrthancStone/Sources/Scene2D/ISceneLayer.h"
+#include "../../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h"
+
+#include <Images/ImageAccessor.h>
+
+
+#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
+  EM_ASM(                                                       \
+    const customEvent = document.createEvent("CustomEvent");    \
+    customEvent.initCustomEvent(name, false, false, undefined); \
+    window.dispatchEvent(customEvent);                          \
+    );
+
+#define EXTERN_CATCH_EXCEPTIONS                         \
+  catch (Orthanc::OrthancException& e)                  \
+  {                                                     \
+    LOG(ERROR) << "OrthancException: " << e.What();     \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (OrthancStone::StoneException& e)               \
+  {                                                     \
+    LOG(ERROR) << "StoneException: " << e.What();       \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (std::exception& e)                             \
+  {                                                     \
+    LOG(ERROR) << "Runtime error: " << e.what();        \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }                                                     \
+  catch (...)                                           \
+  {                                                     \
+    LOG(ERROR) << "Native exception";                   \
+    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
+  }
+
+
+// WARNING: This class can be shared by multiple viewports
+class ILayerSource : public boost::noncopyable
+{
+public:
+  virtual ~ILayerSource()
+  {
+  }
+
+  virtual int GetDepth() const = 0;
+
+  virtual OrthancStone::ISceneLayer* Create(const Orthanc::ImageAccessor& frame,
+                                            const OrthancStone::DicomInstanceParameters& instance,
+                                            unsigned int frameNumber,
+                                            double pixelSpacingX,
+                                            double pixelSpacingY,
+                                            const OrthancStone::CoordinateSystem3D& plane) = 0;
+};
+
+
+class IStoneWebViewerContext : public boost::noncopyable
+{
+public:
+  virtual ~IStoneWebViewerContext()
+  {
+  }
+
+  virtual void RedrawAllViewports() = 0;
+
+  // WARNING: The ImageAccessor will become invalid once leaving the
+  // JavaScript callback, do not keep a reference!
+  virtual bool GetSelectedFrame(Orthanc::ImageAccessor& target /* out */,
+                                std::string& sopInstanceUid /* out */,
+                                unsigned int& frameNumber /* out */,
+                                const std::string& canvas /* in */) = 0;
+};
+
+
+class IStoneWebViewerPlugin : public boost::noncopyable
+{
+public:
+  virtual ~IStoneWebViewerPlugin()
+  {
+  }
+
+  virtual ILayerSource& GetLayerSource() = 0;
+};
--- a/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Tue Apr 22 14:03:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Tue Apr 22 14:29:53 2025 +0200
@@ -45,8 +45,8 @@
 parser.add_argument('--libclang',
                     default = '',
                     help = 'manually provides the path to the libclang shared library')
-parser.add_argument('source', 
-                    help = 'Input C++ file')
+parser.add_argument('sources', nargs='+',
+                    help = 'Input C++ files')
 
 args = parser.parse_args()
 
@@ -57,13 +57,6 @@
 
 index = clang.cindex.Index.create()
 
-# PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of
-# undefined types, which prevents compilation of functions
-tu = index.parse(args.source,
-                 [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))',
-                   '-DSTONE_WEB_VIEWER_EXPORT=__attribute__((annotate("WebAssembly")))'],
-                 options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)
-
 
 
 TEMPLATE = '''
@@ -160,6 +153,9 @@
             elif returnType in [ 'int', 'unsigned int' ]:
                 f['hasReturn'] = True
                 f['returnType'] = "'int'"
+            elif returnType == 'void *':
+                f['hasReturn'] = True
+                f['returnType'] = "'int'"
             else:
                 raise Exception('Unknown return type in function "%s()": %s' % (node.spelling, returnType))
 
@@ -176,6 +172,10 @@
                         arg['type'] = "'string'"
                     elif argType == 'double':
                         arg['type'] = "'double'"
+                    elif argType == 'size_t':
+                        arg['type'] = "'int'"
+                    elif argType in [ 'const void *', 'void *' ]:
+                        arg['type'] = "'int'"
                     else:
                         raise Exception('Unknown type for argument "%s" in function "%s()": %s' %
                                         (child.displayname, node.spelling, argType))
@@ -190,8 +190,15 @@
     for child in node.get_children():
         Explore(child)
 
-Explore(tu.cursor)
 
+for source in args.sources:
+    # PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of
+    # undefined types, which prevents compilation of functions
+    tu = index.parse(source,
+                     [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))',
+                       '-DSTONE_WEB_VIEWER_EXPORT=__attribute__((annotate("WebAssembly")))'],
+                     options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)
+    Explore(tu.cursor)
 
 
 print(pystache.render(TEMPLATE, {
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Apr 22 14:03:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Apr 22 14:29:53 2025 +0200
@@ -20,41 +20,12 @@
  **/
 
 
+#include "IStoneWebViewerContext.h"
+
 #include <EmbeddedResources.h>
 #include <emscripten.h>
 
 
-#define DISPATCH_JAVASCRIPT_EVENT(name)                         \
-  EM_ASM(                                                       \
-    const customEvent = document.createEvent("CustomEvent");    \
-    customEvent.initCustomEvent(name, false, false, undefined); \
-    window.dispatchEvent(customEvent);                          \
-    );
-
-
-#define EXTERN_CATCH_EXCEPTIONS                         \
-  catch (Orthanc::OrthancException& e)                  \
-  {                                                     \
-    LOG(ERROR) << "OrthancException: " << e.What();     \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (OrthancStone::StoneException& e)               \
-  {                                                     \
-    LOG(ERROR) << "StoneException: " << e.What();       \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (std::exception& e)                             \
-  {                                                     \
-    LOG(ERROR) << "Runtime error: " << e.what();        \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }                                                     \
-  catch (...)                                           \
-  {                                                     \
-    LOG(ERROR) << "Native exception";                   \
-    DISPATCH_JAVASCRIPT_EVENT("StoneException");        \
-  }
-
-
 // Orthanc framework includes
 #include <Cache/MemoryObjectCache.h>
 #include <DicomFormat/DicomArray.h>
@@ -84,10 +55,7 @@
 #include "../../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h"
 #include "../../../OrthancStone/Sources/Toolbox/DicomStructuredReport.h"
 #include "../../../OrthancStone/Sources/Toolbox/GeometryToolbox.h"
-#include "../../../OrthancStone/Sources/Toolbox/OsiriX/AngleAnnotation.h"
 #include "../../../OrthancStone/Sources/Toolbox/OsiriX/CollectionOfAnnotations.h"
-#include "../../../OrthancStone/Sources/Toolbox/OsiriX/LineAnnotation.h"
-#include "../../../OrthancStone/Sources/Toolbox/OsiriX/TextAnnotation.h"
 #include "../../../OrthancStone/Sources/Toolbox/SortedFrames.h"
 #include "../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h"
 
@@ -105,6 +73,15 @@
 
 static const double PI = boost::math::constants::pi<double>();
 
+static const int LAYER_TEXTURE = 0;
+static const int LAYER_OVERLAY = 1;
+static const int LAYER_ORIENTATION_MARKERS = 2;
+static const int LAYER_REFERENCE_LINES = 3;
+static const int LAYER_ANNOTATIONS_STONE = 5;
+static const int LAYER_ANNOTATIONS_OSIRIX = 4;
+static const int LAYER_STRUCTURED_REPORT = 6;
+
+
 #if !defined(STONE_WEB_VIEWER_EXPORT)
 // We are not running ParseWebAssemblyExports.py, but we're compiling the wasm
 #  define STONE_WEB_VIEWER_EXPORT
@@ -434,7 +411,7 @@
       return *frames_[index];
     }
   }
-  
+
 public:
   DicomStructuredReportFrames(const OrthancStone::DicomStructuredReport& sr,
                               const OrthancStone::LoadedDicomResources& instances) :
@@ -518,7 +495,7 @@
         frameIndex = i;
       }
     }
-    
+
     return found;
   }
 
@@ -1304,7 +1281,7 @@
           return NULL;
         }
       }
-      
+
       std::unique_ptr<OrthancStone::SortedFrames> target(new OrthancStone::SortedFrames);
       target->Clear();
 
@@ -1469,7 +1446,7 @@
 
 
   static std::string GetKey(const std::string& sopInstanceUid,
-                            size_t frameNumber)
+                            unsigned int frameNumber)
   {
     return sopInstanceUid + "|" + boost::lexical_cast<std::string>(frameNumber);
   }
@@ -1499,7 +1476,7 @@
    * previously cached one, or if no cache was previously available.
    **/
   bool Acquire(const std::string& sopInstanceUid,
-               size_t frameNumber,
+               unsigned int frameNumber,
                Orthanc::ImageAccessor* image /* transfer ownership */,
                unsigned int quality)
   {
@@ -1577,7 +1554,7 @@
   public:
     Accessor(FramesCache& that,
              const std::string& sopInstanceUid,
-             size_t frameNumber) :
+             unsigned int frameNumber) :
       accessor_(that.cache_, GetKey(sopInstanceUid, frameNumber), false /* shared lock */)
     {
     }
@@ -2188,6 +2165,65 @@
 
 
 
+class LayersHolder : public boost::noncopyable
+{
+private:
+  typedef std::map<int, OrthancStone::ISceneLayer*>  Layers;
+
+  Layers layers_;
+
+public:
+  ~LayersHolder()
+  {
+    for (Layers::iterator it = layers_.begin(); it != layers_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        delete it->second;
+      }
+    }
+  }
+
+  void AddLayer(int depth,
+                OrthancStone::ISceneLayer* layer)
+  {
+    std::unique_ptr<OrthancStone::ISceneLayer> protection(layer);
+
+    if (layers_.find(depth) != layers_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Two layer sources are writing to the same depth: " +
+                                      boost::lexical_cast<std::string>(depth));
+    }
+    else if (layer == NULL)
+    {
+      layers_[depth] = NULL;
+    }
+    else
+    {
+      layers_[depth] = protection.release();
+    }
+  }
+
+  void Commit(OrthancStone::Scene2D& scene)
+  {
+    for (Layers::iterator it = layers_.begin(); it != layers_.end(); ++it)
+    {
+      if (it->second == NULL)
+      {
+        scene.DeleteLayer(it->first);
+      }
+      else
+      {
+        std::unique_ptr<OrthancStone::ISceneLayer> layer(it->second);
+        it->second = NULL;
+        scene.SetLayer(it->first, layer.release());
+      }
+    }
+  }
+};
+
+
+
 class ViewerViewport : public OrthancStone::ObserverBase<ViewerViewport>
 {
 public:
@@ -2233,15 +2269,6 @@
   };
 
 private:
-  static const int LAYER_TEXTURE = 0;
-  static const int LAYER_OVERLAY = 1;
-  static const int LAYER_ORIENTATION_MARKERS = 2;
-  static const int LAYER_REFERENCE_LINES = 3;
-  static const int LAYER_ANNOTATIONS_OSIRIX = 4;
-  static const int LAYER_ANNOTATIONS_STONE = 5;
-  static const int LAYER_STRUCTURED_REPORT = 6;
-
-  
   class ICommand : public Orthanc::IDynamicObject
   {
   private:
@@ -2304,22 +2331,24 @@
         OrthancStone::DicomInstanceParameters params(dicom);
         
         params.EnrichUsingDicomWeb(message.GetResources()->GetSourceJson(0));
-        GetViewport().centralPixelSpacingX_ = params.GetPixelSpacingX();
-        GetViewport().centralPixelSpacingY_ = params.GetPixelSpacingY();
 
         if (params.HasPixelSpacing())
         {
+          GetViewport().centralPixelSpacingX_ = params.GetPixelSpacingX();
+          GetViewport().centralPixelSpacingY_ = params.GetPixelSpacingY();
           GetViewport().stoneAnnotations_->SetUnits(OrthancStone::Units_Millimeters);
         }
         else
         {
+          GetViewport().centralPixelSpacingX_ = 1;
+          GetViewport().centralPixelSpacingY_ = 1;
           GetViewport().stoneAnnotations_->SetUnits(OrthancStone::Units_Pixels);
         }
 
-        if (params.GetPixelSpacingX() != 0 &&
-            params.GetPixelSpacingY() != 0 &&
+        if (!OrthancStone::LinearAlgebra::IsNear(params.GetPixelSpacingX(), 0) &&
+            !OrthancStone::LinearAlgebra::IsNear(params.GetPixelSpacingY(), 0) &&
             params.GetWidth() != 0 &&
-            params.GetHeight())
+            params.GetHeight() != 0)
         {
           GetViewport().centralPhysicalWidth_ = (params.GetPixelSpacingX() *
                                                  static_cast<double>(params.GetWidth()));
@@ -2633,20 +2662,22 @@
 
   bool         hasFocusOnInstance_;
   std::string  focusSopInstanceUid_;
-  size_t       focusFrameNumber_;
-
-  // The coordinates of OsiriX annotations are expressed in 3D world coordinates
-  boost::shared_ptr<OrthancStone::OsiriX::CollectionOfAnnotations>  osiriXAnnotations_;
+  unsigned int focusFrameNumber_;
 
   // The coordinates of Stone annotations are expressed in 2D
   // coordinates of the current texture, with (0,0) corresponding to
   // the center of the top-left pixel
   boost::shared_ptr<OrthancStone::AnnotationsSceneLayer>  stoneAnnotations_;
 
+
   bool linearInterpolation_;
   std::string pendingSeriesInstanceUid_;
 
 
+  // WARNING: The ownership is not transferred
+  std::list<ILayerSource*>  layerSources_;
+
+
   void UpdateWindowing(WindowingState state,
                        const OrthancStone::Windowing& windowing)
   {
@@ -2910,81 +2941,16 @@
       layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY);
     }
 
-    std::unique_ptr<OrthancStone::LookupTableTextureSceneLayer> overlay;
-
-    {
-      OverlaysRegistry::Accessor accessor(OverlaysRegistry::GetInstance(), instance.GetSopInstanceUid());
-      if (accessor.IsValid())
-      {
-        overlay.reset(accessor.CreateTexture());
-        overlay->SetLinearInterpolation(false);
-      }
-    }
-
-    std::unique_ptr<OrthancStone::MacroSceneLayer>  annotationsOsiriX;
-
-    if (osiriXAnnotations_)
-    {
-      std::set<size_t> a;
-      osiriXAnnotations_->LookupSopInstanceUid(a, instance.GetSopInstanceUid());
-      if (plane.IsValid() &&
-          !a.empty())
-      {
-        annotationsOsiriX.reset(new OrthancStone::MacroSceneLayer);
-        // annotationsOsiriX->Reserve(a.size());
-
-        OrthancStone::OsiriXLayerFactory factory;
-        factory.SetColor(0, 255, 0);
-          
-        for (std::set<size_t>::const_iterator it = a.begin(); it != a.end(); ++it)
-        {
-          const OrthancStone::OsiriX::Annotation& annotation = osiriXAnnotations_->GetAnnotation(*it);
-          annotationsOsiriX->AddLayer(factory.Create(annotation, plane));
-        }
-      }
-    }
-
     StoneAnnotationsRegistry::GetInstance().Load(*stoneAnnotations_, instance.GetSopInstanceUid(), frameIndex);
 
-    // Orientation markers, new in Stone Web viewer 2.4
-    std::unique_ptr<OrthancStone::MacroSceneLayer>  orientationMarkers;
-
-    if (instance.GetGeometry().IsValid())
-    {
-      orientationMarkers.reset(new OrthancStone::MacroSceneLayer);
-
-      std::string top, bottom, left, right;
-      instance.GetGeometry().GetOrientationMarkers(top, bottom, left, right);
-
-      std::unique_ptr<OrthancStone::TextSceneLayer> text;
-
-      text.reset(new OrthancStone::TextSceneLayer);
-      text->SetText(top);
-      text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()) / 2.0, 0);
-      text->SetAnchor(OrthancStone::BitmapAnchor_TopCenter);
-      orientationMarkers->AddLayer(text.release());
-
-      text.reset(new OrthancStone::TextSceneLayer);
-      text->SetText(bottom);
-      text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()) / 2.0,
-                        pixelSpacingY * static_cast<double>(frame.GetHeight()));
-      text->SetAnchor(OrthancStone::BitmapAnchor_BottomCenter);
-      orientationMarkers->AddLayer(text.release());
-
-      text.reset(new OrthancStone::TextSceneLayer);
-      text->SetText(left);
-      text->SetPosition(0, pixelSpacingY * static_cast<double>(frame.GetHeight()) / 2.0);
-      text->SetAnchor(OrthancStone::BitmapAnchor_CenterLeft);
-      orientationMarkers->AddLayer(text.release());
-
-      text.reset(new OrthancStone::TextSceneLayer);
-      text->SetText(right);
-      text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()),
-                        pixelSpacingY * static_cast<double>(frame.GetHeight()) / 2.0);
-      text->SetAnchor(OrthancStone::BitmapAnchor_CenterRight);
-      orientationMarkers->AddLayer(text.release());
-    }
-
+    LayersHolder holder;
+
+    for (std::list<ILayerSource*>::const_iterator it = layerSources_.begin(); it != layerSources_.end(); ++it)
+    {
+      assert(*it != NULL);
+      holder.AddLayer((*it)->GetDepth(), (*it)->Create(frame, instance, frameIndex,
+                                                       layer->GetPixelSpacingX(), layer->GetPixelSpacingY(), plane));
+    }
 
     std::unique_ptr<OrthancStone::ISceneLayer> structuredReportAnnotations;
 
@@ -2992,63 +2958,19 @@
         frames_->GetType() == FramesCollectionType_DicomSR)
     {
       const DicomStructuredReportFrames& sr = dynamic_cast<const DicomStructuredReportFrames&>(*frames_);
-      structuredReportAnnotations.reset(sr.ExtractAnnotations(instance.GetSopInstanceUid(), frameIndex,
-                                                              layer->GetOriginX(), layer->GetOriginY(),
-                                                              layer->GetPixelSpacingX(), layer->GetPixelSpacingY()));
-    }
-
+      holder.AddLayer(LAYER_STRUCTURED_REPORT, sr.ExtractAnnotations(instance.GetSopInstanceUid(), frameIndex,
+                                                                     layer->GetOriginX(), layer->GetOriginY(),
+                                                                     layer->GetPixelSpacingX(), layer->GetPixelSpacingY()));
+    }
+
+    holder.AddLayer(LAYER_TEXTURE, layer.release());
 
     {
       std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
 
       OrthancStone::Scene2D& scene = lock->GetController().GetScene();
 
-      scene.SetLayer(LAYER_TEXTURE, layer.release());
-
-      if (overlay.get() != NULL)
-      {
-        scene.SetLayer(LAYER_OVERLAY, overlay.release());
-      }
-      else
-      {
-        scene.DeleteLayer(LAYER_OVERLAY);
-      }
-
-      if (annotationsOsiriX.get() != NULL)
-      {
-        scene.SetLayer(LAYER_ANNOTATIONS_OSIRIX, annotationsOsiriX.release());
-      }
-      else
-      {
-        scene.DeleteLayer(LAYER_ANNOTATIONS_OSIRIX);
-      }
-
-      if (orientationMarkers.get() != NULL)
-      {
-        scene.SetLayer(LAYER_ORIENTATION_MARKERS, orientationMarkers.release());
-      }
-      else
-      {
-        scene.DeleteLayer(LAYER_ORIENTATION_MARKERS);
-      }
-
-      if (orientationMarkers.get() != NULL)
-      {
-        scene.SetLayer(LAYER_ORIENTATION_MARKERS, orientationMarkers.release());
-      }
-      else
-      {
-        scene.DeleteLayer(LAYER_ORIENTATION_MARKERS);
-      }
-
-      if (structuredReportAnnotations.get() != NULL)
-      {
-        scene.SetLayer(LAYER_STRUCTURED_REPORT, structuredReportAnnotations.release());
-      }
-      else
-      {
-        scene.DeleteLayer(LAYER_STRUCTURED_REPORT);
-      }
+      holder.Commit(scene);
 
       stoneAnnotations_->Render(scene);  // Necessary for "FitContent()" to work
 
@@ -3076,7 +2998,7 @@
     {
       const size_t cursorIndex = cursor_->GetCurrentIndex();
       const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex);
-      const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
+      const unsigned int frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
 
       // Only change the scene if the loaded frame still corresponds to the current cursor
       if (instance.GetSopInstanceUid() == loadedSopInstanceUid &&
@@ -3377,7 +3299,7 @@
       {
         const size_t cursorIndex = cursor_->GetCurrentIndex();
         const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex);
-        const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
+        const unsigned int frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
 
         StoneAnnotationsRegistry::GetInstance().Save(instance.GetSopInstanceUid(), frameNumber, *stoneAnnotations_);
 
@@ -3482,6 +3404,13 @@
     return viewport;    
   }
 
+
+  void AddLayerSource(ILayerSource& source)  // Ownership is not transfered
+  {
+    layerSources_.push_back(&source);
+  }
+
+
   void SetFrames(IFramesCollection* frames)
   {
     if (frames == NULL)
@@ -3609,7 +3538,7 @@
       const size_t cursorIndex = cursor_->GetCurrentIndex();
 
       const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex);
-      const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
+      const unsigned int frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
 
       FramesCache::Accessor accessor(*framesCache_, instance.GetSopInstanceUid(), frameNumber);
       if (accessor.IsValid())
@@ -4057,11 +3986,6 @@
     viewport_->FitForPrint();
   }
 
-  void SetOsiriXAnnotations(boost::shared_ptr<OrthancStone::OsiriX::CollectionOfAnnotations> annotations)
-  {
-    osiriXAnnotations_ = annotations;
-  }
-
   void ScheduleFrameFocus(const std::string& sopInstanceUid,
                           unsigned int frameNumber)
   {
@@ -4156,7 +4080,7 @@
     {
       const size_t cursorIndex = cursor_->GetCurrentIndex();
       const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex);
-      const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
+      const unsigned int frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
 
       if (instance.GetSopInstanceUid() == sopInstanceUid &&
           frameNumber == frame)
@@ -4190,6 +4114,25 @@
   }
 
 
+  bool GetCurrentFrame(std::string& sopInstanceUid /* out */,
+                       unsigned int& frameNumber /* out */) const
+  {
+    if (cursor_.get() != NULL &&
+        frames_.get() != NULL)
+    {
+      const size_t cursorIndex = cursor_->GetCurrentIndex();
+      const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex);
+      sopInstanceUid = instance.GetSopInstanceUid();
+      frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
   void SignalSynchronizedBrowsing()
   {
     if (synchronizationEnabled_ &&
@@ -4234,11 +4177,156 @@
 
 
 
+class OrientationMarkersSource : public ILayerSource
+{
+public:
+  virtual int GetDepth() const ORTHANC_OVERRIDE
+  {
+    return LAYER_ORIENTATION_MARKERS;
+  }
+
+  virtual OrthancStone::ISceneLayer* Create(const Orthanc::ImageAccessor& frame,
+                                            const OrthancStone::DicomInstanceParameters& instance,
+                                            unsigned int frameNumber,
+                                            double pixelSpacingX,
+                                            double pixelSpacingY,
+                                            const OrthancStone::CoordinateSystem3D& plane) ORTHANC_OVERRIDE
+  {
+    if (instance.GetGeometry().IsValid())
+    {
+      std::unique_ptr<OrthancStone::MacroSceneLayer> layer(new OrthancStone::MacroSceneLayer);
+
+      std::string top, bottom, left, right;
+      instance.GetGeometry().GetOrientationMarkers(top, bottom, left, right);
+
+      std::unique_ptr<OrthancStone::TextSceneLayer> text;
+
+      text.reset(new OrthancStone::TextSceneLayer);
+      text->SetText(top);
+      text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()) / 2.0, 0);
+      text->SetAnchor(OrthancStone::BitmapAnchor_TopCenter);
+      layer->AddLayer(text.release());
+
+      text.reset(new OrthancStone::TextSceneLayer);
+      text->SetText(bottom);
+      text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()) / 2.0,
+                        pixelSpacingY * static_cast<double>(frame.GetHeight()));
+      text->SetAnchor(OrthancStone::BitmapAnchor_BottomCenter);
+      layer->AddLayer(text.release());
+
+      text.reset(new OrthancStone::TextSceneLayer);
+      text->SetText(left);
+      text->SetPosition(0, pixelSpacingY * static_cast<double>(frame.GetHeight()) / 2.0);
+      text->SetAnchor(OrthancStone::BitmapAnchor_CenterLeft);
+      layer->AddLayer(text.release());
+
+      text.reset(new OrthancStone::TextSceneLayer);
+      text->SetText(right);
+      text->SetPosition(pixelSpacingX * static_cast<double>(frame.GetWidth()),
+                        pixelSpacingY * static_cast<double>(frame.GetHeight()) / 2.0);
+      text->SetAnchor(OrthancStone::BitmapAnchor_CenterRight);
+      layer->AddLayer(text.release());
+
+      return layer.release();
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+};
+
+
+class OsiriXLayerSource : public ILayerSource
+{
+private:
+  // The coordinates of OsiriX annotations are expressed in 3D world coordinates
+  OrthancStone::OsiriX::CollectionOfAnnotations  annotations_;
+
+public:
+  OrthancStone::OsiriX::CollectionOfAnnotations& GetAnnotations()
+  {
+    return annotations_;
+  }
+
+  virtual int GetDepth() const ORTHANC_OVERRIDE
+  {
+    return LAYER_ANNOTATIONS_OSIRIX;
+  }
+
+  virtual OrthancStone::ISceneLayer* Create(const Orthanc::ImageAccessor& frame,
+                                            const OrthancStone::DicomInstanceParameters& instance,
+                                            unsigned int frameNumber,
+                                            double pixelSpacingX,
+                                            double pixelSpacingY,
+                                            const OrthancStone::CoordinateSystem3D& plane) ORTHANC_OVERRIDE
+  {
+    std::set<size_t> a;
+    annotations_.LookupSopInstanceUid(a, instance.GetSopInstanceUid());
+    if (plane.IsValid() &&
+        !a.empty())
+    {
+      std::unique_ptr<OrthancStone::MacroSceneLayer> layer(new OrthancStone::MacroSceneLayer);
+      // layer->Reserve(a.size());
+
+      OrthancStone::OsiriXLayerFactory factory;
+      factory.SetColor(0, 255, 0);
+
+      for (std::set<size_t>::const_iterator it = a.begin(); it != a.end(); ++it)
+      {
+        const OrthancStone::OsiriX::Annotation& annotation = annotations_.GetAnnotation(*it);
+        layer->AddLayer(factory.Create(annotation, plane));
+      }
+
+      return layer.release();
+    }
+
+    return NULL;
+  }
+};
+
+
+
+class OverlayLayerSource : public ILayerSource
+{
+public:
+  virtual int GetDepth() const ORTHANC_OVERRIDE
+  {
+    return LAYER_OVERLAY;
+  }
+
+  virtual OrthancStone::ISceneLayer* Create(const Orthanc::ImageAccessor& frame,
+                                            const OrthancStone::DicomInstanceParameters& instance,
+                                            unsigned int frameNumber,
+                                            double pixelSpacingX,
+                                            double pixelSpacingY,
+                                            const OrthancStone::CoordinateSystem3D& plane) ORTHANC_OVERRIDE
+  {
+    OverlaysRegistry::Accessor accessor(OverlaysRegistry::GetInstance(), instance.GetSopInstanceUid());
+    if (accessor.IsValid())
+    {
+      std::unique_ptr<OrthancStone::LookupTableTextureSceneLayer> layer(accessor.CreateTexture());
+      layer->SetLinearInterpolation(false);
+      return layer.release();
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+};
+
+
+
 typedef std::map<std::string, boost::shared_ptr<ViewerViewport> >  Viewports;
 
 static Viewports allViewports_;
 static bool showReferenceLines_ = true;
-static boost::shared_ptr<OrthancStone::OsiriX::CollectionOfAnnotations>  osiriXAnnotations_;
+static std::unique_ptr<OverlayLayerSource>  overlayLayerSource_;
+static std::unique_ptr<OsiriXLayerSource>   osiriXLayerSource_;
+
+// Orientation markers, new in Stone Web viewer 2.4
+static std::unique_ptr<OrientationMarkersSource>  orientationMarkersSource_;
 
 
 static void UpdateReferenceLines()
@@ -4563,6 +4651,15 @@
 }
 
 
+typedef IStoneWebViewerPlugin* (*PluginInitializer) (IStoneWebViewerContext&);
+
+static const PluginInitializer pluginsInitializers_[] = {
+  NULL
+};
+
+static std::list< boost::shared_ptr<IStoneWebViewerPlugin> >  plugins_;
+
+
 static boost::shared_ptr<ViewerViewport> GetViewport(const std::string& canvas)
 {
   Viewports::iterator found = allViewports_.find(canvas);
@@ -4572,7 +4669,15 @@
       ViewerViewport::Create(*context_, source_, canvas, framesCache_, instancesCache_, softwareRendering_, linearInterpolation_));
     viewport->SetMouseButtonActions(leftButtonAction_, middleButtonAction_, rightButtonAction_);
     viewport->AcquireObserver(new WebAssemblyObserver);
-    viewport->SetOsiriXAnnotations(osiriXAnnotations_);
+    viewport->AddLayerSource(*overlayLayerSource_);
+    viewport->AddLayerSource(*osiriXLayerSource_);
+    viewport->AddLayerSource(*orientationMarkersSource_);
+
+    for (std::list< boost::shared_ptr<IStoneWebViewerPlugin> >::iterator it = plugins_.begin(); it != plugins_.end(); ++it)
+    {
+      viewport->AddLayerSource((*it)->GetLayerSource());
+    }
+
     allViewports_[canvas] = viewport;
     return viewport;
   }
@@ -4604,6 +4709,53 @@
 }
 
 
+class StoneWebViewerContext : public IStoneWebViewerContext
+{
+public:
+  static StoneWebViewerContext& GetInstance()
+  {
+    static StoneWebViewerContext instance;
+    return instance;
+  }
+
+  virtual void RedrawAllViewports() ORTHANC_OVERRIDE
+  {
+    for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      it->second->Redraw();
+    }
+  }
+
+  virtual bool GetSelectedFrame(Orthanc::ImageAccessor& target /* out */,
+                                std::string& sopInstanceUid /* out */,
+                                unsigned int& frameNumber /* out */,
+                                const std::string& canvas /* in */) ORTHANC_OVERRIDE
+  {
+    boost::shared_ptr<ViewerViewport> viewport = GetViewport(canvas);
+
+    if (viewport->GetCurrentFrame(sopInstanceUid, frameNumber))
+    {
+      FramesCache::Accessor accessor(*framesCache_, sopInstanceUid, frameNumber);
+      if (accessor.IsValid())
+      {
+        accessor.GetImage().GetReadOnlyAccessor(target);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+    else
+    {
+      LOG(WARNING) << "No active frame";
+      return false;
+    }
+  }
+};
+
+
 extern "C"
 {
   int main(int argc, char const *argv[]) 
@@ -4620,7 +4772,18 @@
     
     framesCache_.reset(new FramesCache);
     instancesCache_.reset(new InstancesCache);
-    osiriXAnnotations_.reset(new OrthancStone::OsiriX::CollectionOfAnnotations);
+    overlayLayerSource_.reset(new OverlayLayerSource);
+    osiriXLayerSource_.reset(new OsiriXLayerSource);
+    orientationMarkersSource_.reset(new OrientationMarkersSource);
+
+    for (size_t i = 0; pluginsInitializers_[i] != NULL; i++)
+    {
+      std::unique_ptr<IStoneWebViewerPlugin> plugin(pluginsInitializers_[i] (StoneWebViewerContext::GetInstance()));
+      if (plugin.get() != NULL)
+      {
+        plugins_.push_back(boost::shared_ptr<IStoneWebViewerPlugin>(plugin.release()));
+      }
+    }
 
     DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
   }
@@ -5221,17 +5384,18 @@
   // Side-effect: "GetStringBuffer()" is filled with the "Series
   // Instance UID" of the first loaded annotation
   EMSCRIPTEN_KEEPALIVE
-  int LoadOsiriXAnnotations(const char* xml,
+  int LoadOsiriXAnnotations(const void* xmlPointer,
+                            int xmlSize,
                             int clearPreviousAnnotations)
   {
     try
     {
       if (clearPreviousAnnotations)
       {
-        osiriXAnnotations_->Clear();
+        osiriXLayerSource_->GetAnnotations().Clear();
       }
-      
-      osiriXAnnotations_->LoadXml(xml);
+
+      osiriXLayerSource_->GetAnnotations().LoadXml(reinterpret_cast<const char*>(xmlPointer), xmlSize);
       
       // Force redraw, as the annotations might have changed
       for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
@@ -5240,16 +5404,16 @@
         it->second->Redraw();
       }
 
-      if (osiriXAnnotations_->GetSize() == 0)
+      if (osiriXLayerSource_->GetAnnotations().GetSize() == 0)
       {
         stringBuffer_.clear();
       }
       else
       {
-        stringBuffer_ = osiriXAnnotations_->GetAnnotation(0).GetSeriesInstanceUid();
+        stringBuffer_ = osiriXLayerSource_->GetAnnotations().GetAnnotation(0).GetSeriesInstanceUid();
       }
 
-      LOG(WARNING) << "Loaded " << osiriXAnnotations_->GetSize() << " annotations from OsiriX";
+      LOG(WARNING) << "Loaded " << osiriXLayerSource_->GetAnnotations().GetSize() << " annotations from OsiriX";
       return 1;
     }
     EXTERN_CATCH_EXCEPTIONS;
@@ -5262,9 +5426,9 @@
   {
     try
     {
-      if (osiriXAnnotations_->GetSize() != 0)
+      if (osiriXLayerSource_->GetAnnotations().GetSize() != 0)
       {
-        const OrthancStone::OsiriX::Annotation& annotation = osiriXAnnotations_->GetAnnotation(0);
+        const OrthancStone::OsiriX::Annotation& annotation = osiriXLayerSource_->GetAnnotations().GetAnnotation(0);
         
         boost::shared_ptr<ViewerViewport> viewport = GetViewport(canvas);
         viewport->ScheduleFrameFocus(annotation.GetSopInstanceUid(), 0 /* focus on first frame */);
@@ -5389,4 +5553,18 @@
     }
     EXTERN_CATCH_EXCEPTIONS;
   }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void *Allocate(size_t size)
+  {
+    return malloc(size);
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void Deallocate(void* ptr)
+  {
+    free(ptr);
+  }
 }
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Apr 22 14:03:14 2025 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Apr 22 14:29:53 2025 +0200
@@ -200,7 +200,6 @@
 if (NOT ORTHANC_SANDBOXED AND ENABLE_THREADS AND ENABLE_WEB_CLIENT)
   list(APPEND ORTHANC_STONE_SOURCES
     ${ORTHANC_STONE_ROOT}/Loaders/GenericLoadersContext.cpp
-    ${ORTHANC_STONE_ROOT}/Loaders/GenericLoadersContext.h
     ${ORTHANC_STONE_ROOT}/Oracle/GenericOracleRunner.cpp
     ${ORTHANC_STONE_ROOT}/Oracle/ThreadedOracle.cpp
     )
@@ -210,7 +209,6 @@
 if (ENABLE_PUGIXML)
   list(APPEND ORTHANC_STONE_SOURCES
     ${ORTHANC_STONE_ROOT}/Scene2D/OsiriXLayerFactory.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/OsiriXLayerFactory.h
     ${ORTHANC_STONE_ROOT}/Toolbox/OsiriX/AngleAnnotation.cpp
     ${ORTHANC_STONE_ROOT}/Toolbox/OsiriX/Annotation.cpp
     ${ORTHANC_STONE_ROOT}/Toolbox/OsiriX/ArrayValue.cpp
@@ -239,36 +237,22 @@
   ${ORTHANC_STONE_ROOT}/Fonts/TextBoundingBox.cpp
 
   ${ORTHANC_STONE_ROOT}/Loaders/BasicFetchingItemsSorter.cpp
-  ${ORTHANC_STONE_ROOT}/Loaders/BasicFetchingItemsSorter.h
   ${ORTHANC_STONE_ROOT}/Loaders/BasicFetchingStrategy.cpp
-  ${ORTHANC_STONE_ROOT}/Loaders/BasicFetchingStrategy.h
   ${ORTHANC_STONE_ROOT}/Loaders/DicomResourcesLoader.cpp
   ${ORTHANC_STONE_ROOT}/Loaders/DicomSource.cpp
   ${ORTHANC_STONE_ROOT}/Loaders/DicomStructureSetLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Loaders/DicomStructureSetLoader.h
   ${ORTHANC_STONE_ROOT}/Loaders/DicomVolumeLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Loaders/IFetchingItemsSorter.h
-  ${ORTHANC_STONE_ROOT}/Loaders/IFetchingStrategy.h
   ${ORTHANC_STONE_ROOT}/Loaders/LoadedDicomResources.cpp
   ${ORTHANC_STONE_ROOT}/Loaders/LoaderStateMachine.cpp
-  ${ORTHANC_STONE_ROOT}/Loaders/LoaderStateMachine.h
   ${ORTHANC_STONE_ROOT}/Loaders/OrthancMultiframeVolumeLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Loaders/OrthancMultiframeVolumeLoader.h
   ${ORTHANC_STONE_ROOT}/Loaders/OracleScheduler.cpp
   ${ORTHANC_STONE_ROOT}/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp
-  ${ORTHANC_STONE_ROOT}/Loaders/OrthancSeriesVolumeProgressiveLoader.h
   ${ORTHANC_STONE_ROOT}/Loaders/SeriesFramesLoader.cpp
   ${ORTHANC_STONE_ROOT}/Loaders/SeriesMetadataLoader.cpp
   ${ORTHANC_STONE_ROOT}/Loaders/SeriesOrderedFrames.cpp
   ${ORTHANC_STONE_ROOT}/Loaders/SeriesThumbnailsLoader.cpp
 
-  ${ORTHANC_STONE_ROOT}/Messages/ICallable.h
-  ${ORTHANC_STONE_ROOT}/Messages/IMessage.h
-  ${ORTHANC_STONE_ROOT}/Messages/IMessageEmitter.h
   ${ORTHANC_STONE_ROOT}/Messages/IObservable.cpp
-  ${ORTHANC_STONE_ROOT}/Messages/IObservable.h
-  ${ORTHANC_STONE_ROOT}/Messages/IObserver.h
-  ${ORTHANC_STONE_ROOT}/Messages/ObserverBase.h
 
   ${ORTHANC_STONE_ROOT}/Oracle/GetOrthancImageCommand.cpp
   ${ORTHANC_STONE_ROOT}/Oracle/GetOrthancWebViewerJpegCommand.cpp
@@ -279,208 +263,103 @@
   ${ORTHANC_STONE_ROOT}/Oracle/ParseDicomFromWadoCommand.cpp
 
   ${ORTHANC_STONE_ROOT}/Scene2D/AnnotationsSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/AnnotationsSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/ArrowSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/ArrowSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/CairoCompositor.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/CairoCompositor.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/Color.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/ColorSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/ColorTextureSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/ColorTextureSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/CopyStyleConfigurator.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/CopyStyleConfigurator.h
   ${ORTHANC_STONE_ROOT}/Scene2D/FloatTextureSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/FloatTextureSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/GrayscaleStyleConfigurator.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/GrayscaleStyleConfigurator.h
   ${ORTHANC_STONE_ROOT}/Scene2D/GrayscaleWindowingSceneTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/GrayscaleWindowingSceneTracker.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/ICompositor.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/ILayerStyleConfigurator.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/ISceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/InfoPanelSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/InfoPanelSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/LookupTableStyleConfigurator.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/LookupTableStyleConfigurator.h
   ${ORTHANC_STONE_ROOT}/Scene2D/LookupTableTextureSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/LookupTableTextureSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/MacroSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/MacroSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/MagnifyingGlassTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/MagnifyingGlassTracker.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/NullLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/PanSceneTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/PanSceneTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2D/PointerEvent.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/PointerEvent.h
   ${ORTHANC_STONE_ROOT}/Scene2D/PolylineSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/PolylineSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/RotateSceneTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/RotateSceneTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Scene2D.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Scene2D.h
   ${ORTHANC_STONE_ROOT}/Scene2D/ScenePoint2D.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/ScenePoint2D.h
   ${ORTHANC_STONE_ROOT}/Scene2D/TextSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/TextSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/TextureBaseSceneLayer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/TextureBaseSceneLayer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/ZoomSceneTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/ZoomSceneTracker.h
 
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoArrowRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoArrowRenderer.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoBaseRenderer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoColorTextureRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoColorTextureRenderer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoFloatTextureRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoFloatTextureRenderer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoInfoPanelRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoInfoPanelRenderer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoLookupTableTextureRenderer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoPolylineRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoPolylineRenderer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoTextRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CairoTextRenderer.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CompositorHelper.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/CompositorHelper.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/FixedPointAligner.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/FixedPointAligner.h
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/ICairoContextProvider.h
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/MacroLayerRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2D/Internals/MacroLayerRenderer.h
   
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/AngleMeasureTool.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/AngleMeasureTool.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateAngleMeasureCommand.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateAngleMeasureCommand.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateAngleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateAngleMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateCircleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateCircleMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateLineMeasureCommand.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateLineMeasureCommand.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateLineMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateLineMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditAngleMeasureCommand.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditAngleMeasureCommand.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditAngleMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditAngleMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditLineMeasureCommand.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditLineMeasureCommand.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditLineMeasureTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/EditLineMeasureTracker.h
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/IFlexiblePointerTracker.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/LayerHolder.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/LayerHolder.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/LineMeasureTool.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/LineMeasureTool.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureCommands.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureCommands.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureTool.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureTool.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureToolsToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureToolsToolbox.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureTrackers.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/MeasureTrackers.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/OneGesturePointerTracker.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/OneGesturePointerTracker.h
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/PredeclaredTypes.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/UndoStack.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/UndoStack.h
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/ViewportController.cpp
-  ${ORTHANC_STONE_ROOT}/Scene2DViewport/ViewportController.h
   ${ORTHANC_STONE_ROOT}/StoneEnumerations.cpp
-  ${ORTHANC_STONE_ROOT}/StoneException.h
   ${ORTHANC_STONE_ROOT}/StoneInitialization.cpp
 
   ${ORTHANC_STONE_ROOT}/Toolbox/AffineTransform2D.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/AffineTransform2D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/BucketAccumulator1D.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/BucketAccumulator1D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/BucketAccumulator2D.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/BucketAccumulator2D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/CoordinateSystem3D.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/CoordinateSystem3D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/DebugDrawing2D.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DebugDrawing2D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/DicomInstanceParameters.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomInstanceParameters.h
   ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DicomStructureSet.h
   ${ORTHANC_STONE_ROOT}/Toolbox/DynamicBitmap.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/DynamicBitmap.h
   ${ORTHANC_STONE_ROOT}/Toolbox/Extent2D.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/Extent2D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/FiniteProjectiveCamera.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/FiniteProjectiveCamera.h
   ${ORTHANC_STONE_ROOT}/Toolbox/GenericToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/GenericToolbox.h
   ${ORTHANC_STONE_ROOT}/Toolbox/GeometryToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/GeometryToolbox.h
   ${ORTHANC_STONE_ROOT}/Toolbox/ImageGeometry.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/ImageGeometry.h
   ${ORTHANC_STONE_ROOT}/Toolbox/ImageToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/ImageToolbox.h
   ${ORTHANC_STONE_ROOT}/Toolbox/Internals/BucketMapper.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/Internals/BucketMapper.h
   ${ORTHANC_STONE_ROOT}/Toolbox/Internals/OrientedIntegerLine2D.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/Internals/OrientedIntegerLine2D.h
   ${ORTHANC_STONE_ROOT}/Toolbox/Internals/RectanglesIntegerProjection.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/Internals/RectanglesIntegerProjection.h
   ${ORTHANC_STONE_ROOT}/Toolbox/LinearAlgebra.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/LinearAlgebra.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/PixelTestPatterns.h
   ${ORTHANC_STONE_ROOT}/Toolbox/SegmentTree.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/SegmentTree.h
   ${ORTHANC_STONE_ROOT}/Toolbox/ShearWarpProjectiveTransform.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/ShearWarpProjectiveTransform.h
   ${ORTHANC_STONE_ROOT}/Toolbox/SlicesSorter.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/SlicesSorter.h
   ${ORTHANC_STONE_ROOT}/Toolbox/SortedFrames.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/SortedFrames.h
   ${ORTHANC_STONE_ROOT}/Toolbox/StoneToolbox.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/StoneToolbox.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/SubpixelReader.h
-  ${ORTHANC_STONE_ROOT}/Toolbox/SubvoxelReader.h
   ${ORTHANC_STONE_ROOT}/Toolbox/TextRenderer.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/TextRenderer.h
   ${ORTHANC_STONE_ROOT}/Toolbox/UndoRedoStack.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/UndoRedoStack.h
   ${ORTHANC_STONE_ROOT}/Toolbox/UnionOfRectangles.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/UnionOfRectangles.h
   ${ORTHANC_STONE_ROOT}/Toolbox/Windowing.cpp
-  ${ORTHANC_STONE_ROOT}/Toolbox/Windowing.h
   
   ${ORTHANC_STONE_ROOT}/Viewport/DefaultViewportInteractor.cpp
-  ${ORTHANC_STONE_ROOT}/Viewport/IViewport.h
   ${ORTHANC_STONE_ROOT}/Viewport/ViewportLocker.cpp
   
-  ${ORTHANC_STONE_ROOT}/Volumes/IGeometryProvider.h
   ${ORTHANC_STONE_ROOT}/Volumes/IVolumeSlicer.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/IVolumeSlicer.h
   ${ORTHANC_STONE_ROOT}/Volumes/OrientedVolumeBoundingBox.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/OrientedVolumeBoundingBox.h
 
   ${ORTHANC_STONE_ROOT}/Volumes/VolumeImageGeometry.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/VolumeImageGeometry.h
   ${ORTHANC_STONE_ROOT}/Volumes/VolumeReslicer.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/VolumeReslicer.h
   ${ORTHANC_STONE_ROOT}/Volumes/VolumeSceneLayerSource.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/VolumeSceneLayerSource.h
-  ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.h
   ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImage.h
   ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImageMPRSlicer.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImageMPRSlicer.h
   ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImageReslicer.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/DicomVolumeImageReslicer.h
   ${ORTHANC_STONE_ROOT}/Volumes/ImageBuffer3D.cpp
-  ${ORTHANC_STONE_ROOT}/Volumes/ImageBuffer3D.h
 
   ${ORTHANC_STONE_ROOT}/Wrappers/CairoContext.cpp
   ${ORTHANC_STONE_ROOT}/Wrappers/CairoSurface.cpp
@@ -502,44 +381,25 @@
 
 if (ENABLE_OPENGL)
   list(APPEND ORTHANC_STONE_SOURCES
-    ${ORTHANC_STONE_ROOT}/Fonts/OpenGLTextCoordinates.h
     ${ORTHANC_STONE_ROOT}/Fonts/OpenGLTextCoordinates.cpp
-    ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLProgram.h
     ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLProgram.cpp
-    ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLShader.h
     ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLShader.cpp
-    ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLTexture.h
     ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLTexture.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/OpenGLCompositor.h
     ${ORTHANC_STONE_ROOT}/Scene2D/OpenGLCompositor.cpp
 
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLArrowRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLArrowRenderer.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLBasicPolylineRenderer.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLColorTextureProgram.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLColorTextureProgram.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLColorTextureRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLColorTextureRenderer.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLFloatTextureProgram.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLFloatTextureProgram.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLFloatTextureRenderer.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLInfoPanelRenderer.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLLinesProgram.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLLinesProgram.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLLookupTableTextureRenderer.h
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLShaderVersionDirective.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLTextProgram.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLTextProgram.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLTextRenderer.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLTextRenderer.h
     ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLTextureProgram.cpp
-    ${ORTHANC_STONE_ROOT}/Scene2D/Internals/OpenGLTextureProgram.h
     )
 endif()
 
@@ -577,4 +437,4 @@
 endmacro()
 
 
-AutodetectHeaderFiles(ORTHANC_STONE_SOURCES)
+# AutodetectHeaderFiles(ORTHANC_STONE_SOURCES)