changeset 2203:dcfabb36dc21

introducing a plugin system to refactor deep-learning
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 19 Apr 2025 14:40:38 +0200 (6 weeks ago)
parents be298b7f5469
children e1613509a939 b36f7b6eec39 c1cc403eae8e
files Applications/StoneWebViewer/WebAssembly/IStoneWebViewerContext.h Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp
diffstat 3 files changed, 201 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/IStoneWebViewerContext.h	Sat Apr 19 14:40:38 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	Sat Apr 19 11:47:04 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Sat Apr 19 14:40:38 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 = '''
@@ -197,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	Sat Apr 19 11:47:04 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Sat Apr 19 14:40:38 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>
@@ -1789,25 +1760,6 @@
 
 
 
-// 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 LayersHolder : public boost::noncopyable
 {
 private:
@@ -3742,6 +3694,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_ &&
@@ -4238,6 +4209,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);
@@ -4250,6 +4230,12 @@
     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;
   }
@@ -4260,15 +4246,53 @@
 }
 
 
-
-typedef void (*PluginInitializer) ();
-
-static const PluginInitializer pluginsInitializers_[] = {
-  NULL
+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[]) 
@@ -4291,7 +4315,11 @@
 
     for (size_t i = 0; pluginsInitializers_[i] != NULL; i++)
     {
-      pluginsInitializers_[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");