changeset 2199:e02471b1bce1 deep-learning

integration mainline->deep-learning
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 19 Apr 2025 11:29:01 +0200 (6 weeks ago)
parents fe1ee95aa12c (current diff) 5a16051c7f3c (diff)
children 5251dad99350
files Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp
diffstat 1 files changed, 268 insertions(+), 132 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Apr 18 17:46:10 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Sat Apr 19 11:29:01 2025 +0200
@@ -83,10 +83,7 @@
 #include "../../../OrthancStone/Sources/StoneException.h"
 #include "../../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.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"
 
@@ -104,6 +101,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_DEEP_LEARNING = 6;
+
+
 #if !defined(STONE_WEB_VIEWER_EXPORT)
 // We are not running ParseWebAssemblyExports.py, but we're compiling the wasm
 #  define STONE_WEB_VIEWER_EXPORT
@@ -1784,6 +1790,84 @@
 
 
 
+// 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:
+  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:
@@ -1829,15 +1913,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_DEEP_LEARNING = 6;
-
-  
   class ICommand : public Orthanc::IDynamicObject
   {
   private:
@@ -2231,20 +2306,21 @@
   std::string  focusSopInstanceUid_;
   size_t       focusFrameNumber_;
 
-  // The coordinates of OsiriX annotations are expressed in 3D world coordinates
-  boost::shared_ptr<OrthancStone::OsiriX::CollectionOfAnnotations>  osiriXAnnotations_;
-
   // 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_;
 
   boost::shared_ptr<Orthanc::ImageAccessor>  deepLearningMask_;
   std::string deepLearningSopInstanceUid_;
   unsigned int deepLearningFrameNumber_;
 
+  // WARNING: The ownership is not transferred
+  std::list<ILayerSource*>  layerSources_;
+
+
   void UpdateWindowing(WindowingState state,
                        const OrthancStone::Windowing& windowing)
   {
@@ -2508,40 +2584,6 @@
       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));
-        }
-      }
-    }
-
     std::unique_ptr<OrthancStone::LookupTableTextureSceneLayer> deepLearningLayer;
 
     if (deepLearningMask_.get() != NULL &&
@@ -2564,79 +2606,22 @@
 
     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;
+
+    holder.AddLayer(LAYER_TEXTURE, layer.release());
+
+    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, pixelSpacingX, pixelSpacingY, plane));
+    }
 
     {
       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);
-      }
+      holder.Commit(scene);
 
       if (deepLearningLayer.get() != NULL)
       {
@@ -3079,6 +3064,13 @@
     return viewport;    
   }
 
+
+  void AddLayerSource(ILayerSource& source)  // Ownership is not transfered
+  {
+    layerSources_.push_back(&source);
+  }
+
+
   void SetFrames(IFramesCollection* frames)
   {
     if (frames == NULL)
@@ -3654,11 +3646,6 @@
     viewport_->FitForPrint();
   }
 
-  void SetOsiriXAnnotations(boost::shared_ptr<OrthancStone::OsiriX::CollectionOfAnnotations> annotations)
-  {
-    osiriXAnnotations_ = annotations;
-  }
-
   void ScheduleFrameFocus(const std::string& sopInstanceUid,
                           unsigned int frameNumber)
   {
@@ -3852,11 +3839,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()
@@ -4186,7 +4318,9 @@
       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_);
     allViewports_[canvas] = viewport;
     return viewport;
   }
@@ -4441,7 +4575,9 @@
     
     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++)
     {
@@ -5081,10 +5217,10 @@
     {
       if (clearPreviousAnnotations)
       {
-        osiriXAnnotations_->Clear();
+        osiriXLayerSource_->GetAnnotations().Clear();
       }
 
-      osiriXAnnotations_->LoadXml(reinterpret_cast<const char*>(xmlPointer), xmlSize);
+      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)
@@ -5093,16 +5229,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;
@@ -5115,9 +5251,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 */);