changeset 2204:e1613509a939 deep-learning tip

integration default->deep-learning, turning deep learning into plugin
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 19 Apr 2025 14:46:27 +0200 (43 hours ago)
parents 2795f1ee4a1a (current diff) dcfabb36dc21 (diff)
children
files Applications/StoneWebViewer/WebAssembly/CMakeLists.txt Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp
diffstat 4 files changed, 179 insertions(+), 394 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Sat Apr 19 11:47:20 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Sat Apr 19 14:46:27 2025 +0200
@@ -114,7 +114,7 @@
 
 add_custom_command(
   COMMAND
-  ${CLANG_PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py --libclang=${LIBCLANG} ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp > ${STONE_WRAPPER}
+  ${CLANG_PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py --libclang=${LIBCLANG} ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp ${CMAKE_SOURCE_DIR}/DeepLearning.cpp > ${STONE_WRAPPER}
   DEPENDS
   ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp
   ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py
@@ -151,6 +151,7 @@
   ${ORTHANC_STONE_SOURCES}
   ${AUTOGENERATED_SOURCES}  # Populated by "EmbedResources()"
   ${AUTOGENERATED_DIR}/DeepLearningWorker.pb.cc
+  DeepLearning.cpp
   StoneWebViewer.cpp
   )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/StoneWebViewer/WebAssembly/IStoneWebViewerContext.h	Sat Apr 19 14:46:27 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:20 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Sat Apr 19 14:46:27 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:20 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Sat Apr 19 14:46:27 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>
@@ -107,7 +78,6 @@
 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_DEEP_LEARNING = 6;
 
 
 #if !defined(STONE_WEB_VIEWER_EXPORT)
@@ -1790,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:
@@ -3927,70 +3878,6 @@
 
 
 
-class DeepLearningSegmentationSource : public ILayerSource
-{
-private:
-  std::unique_ptr<Orthanc::ImageAccessor>  mask_;
-  std::string sopInstanceUid_;
-  unsigned int frameNumber_;
-
-public:
-  DeepLearningSegmentationSource() :
-    frameNumber_(0)  // Dummy initialization
-  {
-  }
-
-  virtual int GetDepth() const ORTHANC_OVERRIDE
-  {
-    return LAYER_DEEP_LEARNING;
-  }
-
-  void SetMask(const std::string& sopInstanceUid,
-               unsigned int frameNumber,
-               const Orthanc::ImageAccessor& mask)
-  {
-    sopInstanceUid_ = sopInstanceUid;
-    frameNumber_ = frameNumber;
-    mask_.reset(Orthanc::Image::Clone(mask));
-  }
-
-  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 (mask_.get() != NULL &&
-        sopInstanceUid_ == instance.GetSopInstanceUid() &&
-        frameNumber_ == frameNumber)
-    {
-      std::unique_ptr<OrthancStone::LookupTableTextureSceneLayer> layer;
-
-      std::vector<uint8_t> lut(4 * 256);
-      for (unsigned int v = 128; v < 256; v++)
-      {
-        lut[4 * v] = 196;
-        lut[4 * v + 1] = 0;
-        lut[4 * v + 2] = 0;
-        lut[4 * v + 3] = 196;
-      }
-
-      layer.reset(new OrthancStone::LookupTableTextureSceneLayer(*mask_));
-      layer->SetLookupTable(lut);
-      layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY);
-
-      return layer.release();
-    }
-    else
-    {
-      return NULL;
-    }
-  }
-};
-
-
-
 typedef std::map<std::string, boost::shared_ptr<ViewerViewport> >  Viewports;
 
 static Viewports allViewports_;
@@ -4001,8 +3888,6 @@
 // Orientation markers, new in Stone Web viewer 2.4
 static std::unique_ptr<OrientationMarkersSource>  orientationMarkersSource_;
 
-static std::unique_ptr<DeepLearningSegmentationSource>  deepLearningSegmentationSource_;
-
 static void UpdateReferenceLines()
 {
   if (showReferenceLines_)
@@ -4321,6 +4206,18 @@
 }
 
 
+IStoneWebViewerPlugin* DeepLearningInitialization(IStoneWebViewerContext& context);
+
+typedef IStoneWebViewerPlugin* (*PluginInitializer) (IStoneWebViewerContext&);
+
+static const PluginInitializer pluginsInitializers_[] = {
+  DeepLearningInitialization,
+  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);
@@ -4334,7 +4231,10 @@
     viewport->AddLayerSource(*osiriXLayerSource_);
     viewport->AddLayerSource(*orientationMarkersSource_);
 
-    viewport->AddLayerSource(*deepLearningSegmentationSource_);
+    for (std::list< boost::shared_ptr<IStoneWebViewerPlugin> >::iterator it = plugins_.begin(); it != plugins_.end(); ++it)
+    {
+      viewport->AddLayerSource((*it)->GetLayerSource());
+    }
 
     allViewports_[canvas] = viewport;
     return viewport;
@@ -4346,233 +4246,50 @@
 }
 
 
-#include <emscripten/fetch.h>
-#include <DeepLearningWorker.pb.h>
-
-enum DeepLearningState
-{
-  DeepLearningState_Waiting,
-  DeepLearningState_Pending,
-  DeepLearningState_Running
-};
-
-static DeepLearningState deepLearningState_ = DeepLearningState_Waiting;
-static worker_handle deepLearningWorker_;
-static std::string deepLearningPendingSopInstanceUid_;
-static unsigned int deepLearningPendingFrameNumber_;
-
-// Forward declaration
-static void DeepLearningCallback(char* data,
-                                 int size,
-                                 void* payload);
-
-static void SendRequestToWebWorker(const OrthancStone::Messages::Request& request)
-{
-  std::string s;
-  if (request.SerializeToString(&s) &&
-      !s.empty())
-  {
-    emscripten_call_worker(deepLearningWorker_, "Execute", &s[0], s.size(), DeepLearningCallback, NULL);
-  }
-  else
-  {
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                    "Cannot send command to the Web worker");
-  }
-}
-
-static void DeepLearningSchedule(const std::string& sopInstanceUid,
-                                 unsigned int frameNumber)
-{
-  if (deepLearningState_ == DeepLearningState_Waiting)
-  {
-    LOG(WARNING) << "Starting deep learning on: " << sopInstanceUid << " / " << frameNumber;
-
-    FramesCache::Accessor accessor(*framesCache_, sopInstanceUid, frameNumber);
-    if (accessor.IsValid() &&
-        accessor.GetImage().GetFormat() == Orthanc::PixelFormat_Float32)
-    {
-      const Orthanc::ImageAccessor& image = accessor.GetImage();
-
-      OrthancStone::Messages::Request request;
-      request.set_type(OrthancStone::Messages::RequestType::LOAD_IMAGE);
-      request.mutable_load_image()->set_sop_instance_uid(sopInstanceUid);
-      request.mutable_load_image()->set_frame_number(frameNumber);
-      request.mutable_load_image()->set_width(image.GetWidth());
-      request.mutable_load_image()->set_height(image.GetHeight());
-
-      const unsigned int height = image.GetHeight();
-      const unsigned int width = image.GetWidth();
-      for (unsigned int y = 0; y < height; y++)
-      {
-        const float* p = reinterpret_cast<const float*>(image.GetConstRow(y));
-        for (unsigned int x = 0; x < width; x++, p++)
-        {
-          request.mutable_load_image()->mutable_values()->Add(*p);
-        }
-      }
-
-      deepLearningState_ = DeepLearningState_Running;
-      SendRequestToWebWorker(request);
-    }
-    else
-    {
-      LOG(ERROR) << "Cannot access the frame content, maybe a color image?";
-
-      EM_ASM({
-          const customEvent = document.createEvent("CustomEvent");
-          customEvent.initCustomEvent("DeepLearningStep", false, false,
-                                      { "progress" : "0" });
-          window.dispatchEvent(customEvent);
-        });
-    }
-  }
-  else
-  {
-    deepLearningState_ = DeepLearningState_Pending;
-    deepLearningPendingSopInstanceUid_ = sopInstanceUid;
-    deepLearningPendingFrameNumber_ = frameNumber;
-  }
-}
-
-static void DeepLearningNextStep()
+class StoneWebViewerContext : public IStoneWebViewerContext
 {
-  switch (deepLearningState_)
-  {
-    case DeepLearningState_Pending:
-      deepLearningState_ = DeepLearningState_Waiting;
-      DeepLearningSchedule(deepLearningPendingSopInstanceUid_, deepLearningPendingFrameNumber_);
-      break;
-
-    case DeepLearningState_Running:
-    {
-      OrthancStone::Messages::Request request;
-      request.set_type(OrthancStone::Messages::RequestType::EXECUTE_STEP);
-      SendRequestToWebWorker(request);
-      break;
-    }
-
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Bad state for deep learning");
-  }
-}
-
-static void DeepLearningCallback(char* data,
-                                 int size,
-                                 void* payload)
-{
-  try
-  {
-    OrthancStone::Messages::Response response;
-    if (response.ParseFromArray(data, size))
-    {
-      switch (response.type())
+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())
       {
-        case OrthancStone::Messages::ResponseType::INITIALIZED:
-          DISPATCH_JAVASCRIPT_EVENT("DeepLearningInitialized");
-          break;
-
-        case OrthancStone::Messages::ResponseType::PARSED_MODEL:
-          LOG(WARNING) << "Number of steps in the model: " << response.parse_model().number_of_steps();
-          DISPATCH_JAVASCRIPT_EVENT("DeepLearningModelReady");
-          break;
-
-        case OrthancStone::Messages::ResponseType::LOADED_IMAGE:
-          DeepLearningNextStep();
-          break;
-
-        case OrthancStone::Messages::ResponseType::STEP_DONE:
-        {
-          EM_ASM({
-              const customEvent = document.createEvent("CustomEvent");
-              customEvent.initCustomEvent("DeepLearningStep", false, false,
-                                          { "progress" : $0 });
-              window.dispatchEvent(customEvent);
-            },
-            response.step().progress()
-            );
-
-          if (response.step().done())
-          {
-            deepLearningState_ = DeepLearningState_Waiting;
-
-            const unsigned int height = response.step().mask().height();
-            const unsigned int width = response.step().mask().width();
-
-            LOG(WARNING) << "SUCCESS! Mask: " << width << "x" << height << " for frame "
-                         << response.step().mask().sop_instance_uid() << " / "
-                         << response.step().mask().frame_number();
-
-            Orthanc::Image mask(Orthanc::PixelFormat_Grayscale8, width, height, false);
-
-            size_t pos = 0;
-            for (unsigned int y = 0; y < height; y++)
-            {
-              uint8_t* p = reinterpret_cast<uint8_t*>(mask.GetRow(y));
-              for (unsigned int x = 0; x < width; x++, p++, pos++)
-              {
-                *p = response.step().mask().values(pos) ? 255 : 0;
-              }
-            }
-
-            deepLearningSegmentationSource_->SetMask(response.step().mask().sop_instance_uid(),
-                                                     response.step().mask().frame_number(), mask);
-
-            for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
-            {
-              assert(it->second != NULL);
-              it->second->Redraw();
-            }
-          }
-          else
-          {
-            DeepLearningNextStep();
-          }
-
-          break;
-        }
-
-        default:
-          LOG(ERROR) << "Unsupported response type from the deep learning worker";
+        accessor.GetImage().GetReadOnlyAccessor(target);
+        return true;
+      }
+      else
+      {
+        return false;
       }
     }
     else
     {
-      LOG(ERROR) << "Bad response received from the deep learning worker";
-    }
-  }
-  EXTERN_CATCH_EXCEPTIONS;
-}
-
-static void DeepLearningModelLoaded(emscripten_fetch_t *fetch)
-{
-  try
-  {
-    LOG(WARNING) << "Deep learning model loaded: " << fetch->numBytes;
-
-    OrthancStone::Messages::Request request;
-    request.set_type(OrthancStone::Messages::RequestType::PARSE_MODEL);
-    request.mutable_parse_model()->mutable_content()->assign(fetch->data, fetch->numBytes);
-
-    emscripten_fetch_close(fetch);  // Don't use "fetch" below
-    SendRequestToWebWorker(request);
-  }
-  EXTERN_CATCH_EXCEPTIONS;
-}
-
-
-static void DeepLearningInitialization()
-{
-  deepLearningWorker_ = emscripten_create_worker("../stone-deep-learning/DeepLearningWorker.js");
-  emscripten_call_worker(deepLearningWorker_, "Initialize", NULL, 0, DeepLearningCallback, NULL);
-}
-
-
-typedef void (*PluginInitializer) ();
-
-static const PluginInitializer pluginsInitializers_[] = {
-  DeepLearningInitialization,
-  NULL
+      LOG(WARNING) << "No active frame";
+      return false;
+    }
+  }
 };
 
 
@@ -4596,11 +4313,13 @@
     osiriXLayerSource_.reset(new OsiriXLayerSource);
     orientationMarkersSource_.reset(new OrientationMarkersSource);
 
-    deepLearningSegmentationSource_.reset(new DeepLearningSegmentationSource);
-
     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");
@@ -4608,47 +4327,6 @@
 
 
   EMSCRIPTEN_KEEPALIVE
-  void LoadDeepLearningModel(const char* uri)
-  {
-    try
-    {
-      LOG(WARNING) << "Loading deep learning model: " << uri;
-
-      emscripten_fetch_attr_t attr;
-      emscripten_fetch_attr_init(&attr);
-      strcpy(attr.requestMethod, "GET");
-      attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
-      attr.onsuccess = DeepLearningModelLoaded;
-      attr.onerror = NULL;
-      emscripten_fetch(&attr, uri);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void ApplyDeepLearningModel(const char* canvas)
-  {
-    try
-    {
-      boost::shared_ptr<ViewerViewport> viewport = GetViewport(canvas);
-
-      std::string sopInstanceUid;
-      unsigned int frameNumber;
-      if (viewport->GetCurrentFrame(sopInstanceUid, frameNumber))
-      {
-        DeepLearningSchedule(sopInstanceUid, frameNumber);
-      }
-      else
-      {
-        LOG(WARNING) << "No active frame";
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
   void SetDicomWebRoot(const char* uri,
                        int useRendered)
   {