diff Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp @ 2003:963f28eb40cb deep-learning

integration default->deep-learning
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 02 Nov 2022 15:14:56 +0100
parents 2034ae383cfd e943a84da9ac
children 37d6805b80ee
line wrap: on
line diff
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Oct 28 07:47:55 2022 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Wed Nov 02 15:14:56 2022 +0100
@@ -96,10 +96,13 @@
 #include "../../../OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.h"
 
 
-#include <boost/math/special_functions/round.hpp>
+#include <algorithm>
 #include <boost/make_shared.hpp>
+#include <boost/math/constants/constants.hpp>
+#include <boost/math/special_functions/round.hpp>
 #include <stdio.h>
-#include <algorithm>
+
+static const double PI = boost::math::constants::pi<double>();
 
 #if !defined(STONE_WEB_VIEWER_EXPORT)
 // We are not running ParseWebAssemblyExports.py, but we're compiling the wasm
@@ -138,11 +141,16 @@
     WebViewerAction_Pan,
     WebViewerAction_Rotate,
     WebViewerAction_Crosshair,
+    WebViewerAction_MagnifyingGlass,       // New in 2.4
     
     WebViewerAction_CreateAngle,
     WebViewerAction_CreateCircle,
-    WebViewerAction_CreateSegment,
-    WebViewerAction_RemoveMeasure
+    WebViewerAction_CreateLength,
+    WebViewerAction_RemoveMeasure,
+    WebViewerAction_CreatePixelProbe,      // New in 2.4
+    WebViewerAction_CreateEllipseProbe,    // New in 2.4
+    WebViewerAction_CreateRectangleProbe,  // New in 2.4
+    WebViewerAction_CreateTextAnnotation   // New in 2.4
     };
   
 
@@ -163,12 +171,19 @@
     case WebViewerAction_Rotate:
       return OrthancStone::MouseAction_Rotate;
       
+    case WebViewerAction_MagnifyingGlass:
+      return OrthancStone::MouseAction_MagnifyingGlass;
+      
     case WebViewerAction_None:
     case WebViewerAction_Crosshair:
     case WebViewerAction_CreateAngle:
     case WebViewerAction_CreateCircle:
-    case WebViewerAction_CreateSegment:
+    case WebViewerAction_CreateLength:
     case WebViewerAction_RemoveMeasure:
+    case WebViewerAction_CreatePixelProbe:
+    case WebViewerAction_CreateEllipseProbe:
+    case WebViewerAction_CreateRectangleProbe:
+    case WebViewerAction_CreateTextAnnotation:
       return OrthancStone::MouseAction_None;
 
     default:
@@ -1198,9 +1213,10 @@
   }
   
 public:
-  explicit SeriesCursor(size_t framesCount) :
+  explicit SeriesCursor(size_t framesCount,
+                        bool startAtMiddle /* Whether to start at the middle frame */) :
     framesCount_(framesCount),
-    currentFrame_(framesCount / 2),  // Start at the middle frame    
+    currentFrame_(startAtMiddle ? framesCount / 2 : 0),
     isCircularPrefetch_(false),
     lastAction_(Action_None)
   {
@@ -1581,16 +1597,20 @@
     virtual void SignalStoneAnnotationAdded(const ViewerViewport& viewport) = 0;
 
     virtual void SignalStoneAnnotationRemoved(const ViewerViewport& viewport) = 0;
+
+    virtual void SignalStoneTextAnnotationRequired(const ViewerViewport& viewport,
+                                                   const OrthancStone::ScenePoint2D& pointedPosition,
+                                                   const OrthancStone::ScenePoint2D& labelPosition) = 0;
   };
 
 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;
+  static const int LAYER_DEEP_LEARNING = 2;
+  static const int LAYER_ORIENTATION_MARKERS = 3;
+  static const int LAYER_REFERENCE_LINES = 4;
+  static const int LAYER_ANNOTATIONS_OSIRIX = 5;
+  static const int LAYER_ANNOTATIONS_STONE = 6;
 
   
   class ICommand : public Orthanc::IDynamicObject
@@ -1985,8 +2005,6 @@
   std::vector<float>                           windowingPresetWidths_;
   unsigned int                                 cineRate_;
   bool                                         inverted_;
-  bool                                         flipX_;
-  bool                                         flipY_;
   bool                                         fitNextContent_;
   std::list<PrefetchItem>                      prefetchQueue_;
   bool                                         serverSideTranscoding_;
@@ -2008,11 +2026,12 @@
   // 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_;
-  
 
   void ScheduleNextPrefetch()
   {
@@ -2150,9 +2169,7 @@
 
     assert(layer.get() != NULL);
 
-    layer->SetLinearInterpolation(true);
-    layer->SetFlipX(flipX_);
-    layer->SetFlipY(flipY_);
+    layer->SetLinearInterpolation(linearInterpolation_);
 
     double pixelSpacingX, pixelSpacingY;
 
@@ -2206,8 +2223,7 @@
       if (accessor.IsValid())
       {
         overlay.reset(accessor.CreateTexture());
-        overlay->SetFlipX(flipX_);
-        overlay->SetFlipY(flipY_);
+        overlay->SetLinearInterpolation(false);
       }
     }
 
@@ -2252,8 +2268,6 @@
       deepLearningLayer.reset(new OrthancStone::LookupTableTextureSceneLayer(*deepLearningMask_));
       deepLearningLayer->SetLookupTable(lut);
       deepLearningLayer->SetPixelSpacing(pixelSpacingX, pixelSpacingY);
-      deepLearningLayer->SetFlipX(flipX_);
-      deepLearningLayer->SetFlipY(flipY_);
     }
 
     StoneAnnotationsRegistry::GetInstance().Load(*stoneAnnotations_, instance.GetSopInstanceUid(), frameIndex);
@@ -2523,25 +2537,6 @@
           lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)).
           SetCustomWindowing(windowingCenter_, windowingWidth_);
       }
-
-      {
-        OrthancStone::TextureBaseSceneLayer& layer = 
-          dynamic_cast<OrthancStone::TextureBaseSceneLayer&>(
-            lock->GetController().GetScene().GetLayer(LAYER_TEXTURE));
-
-        layer.SetFlipX(flipX_);
-        layer.SetFlipY(flipY_);
-      }
-
-      if (lock->GetController().GetScene().HasLayer(LAYER_OVERLAY))
-      {
-        OrthancStone::TextureBaseSceneLayer& layer = 
-          dynamic_cast<OrthancStone::TextureBaseSceneLayer&>(
-            lock->GetController().GetScene().GetLayer(LAYER_OVERLAY));
-
-        layer.SetFlipX(flipX_);
-        layer.SetFlipY(flipY_);
-      }
         
       lock->Invalidate();
     }
@@ -2552,13 +2547,12 @@
                  const OrthancStone::DicomSource& source,
                  const std::string& canvas,
                  boost::shared_ptr<FramesCache> cache,
-                 bool softwareRendering) :
+                 bool softwareRendering,
+                 bool linearInterpolation) :
     context_(context),
     source_(source),
     framesCache_(cache),
     fitNextContent_(true),
-    flipX_(false),
-    flipY_(false),
     hasFocusOnInstance_(false),
     focusFrameNumber_(0),
     synchronizationOffset_(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0)),
@@ -2566,7 +2560,8 @@
     centralPhysicalWidth_(1),
     centralPhysicalHeight_(1),
     centralPixelSpacingX_(1),
-    centralPixelSpacingY_(1)
+    centralPixelSpacingY_(1),
+    linearInterpolation_(linearInterpolation)
   {
     if (!framesCache_)
     {
@@ -2596,6 +2591,7 @@
     SetWindowingPreset();
 
     stoneAnnotations_.reset(new OrthancStone::AnnotationsSceneLayer(LAYER_ANNOTATIONS_STONE));
+    stoneAnnotations_->SetProbedLayer(LAYER_TEXTURE);
   }
 
 
@@ -2733,6 +2729,14 @@
     }
   }
 
+  void Handle(const OrthancStone::AnnotationsSceneLayer::TextAnnotationRequiredMessage& message)
+  {
+    if (observer_.get() != NULL)
+    {
+      observer_->SignalStoneTextAnnotationRequired(*this, message.GetPointedPosition(), message.GetLabelPosition());
+    }
+  }
+
 public:
   virtual ~ViewerViewport()
   {
@@ -2746,10 +2750,11 @@
                                                   const OrthancStone::DicomSource& source,
                                                   const std::string& canvas,
                                                   boost::shared_ptr<FramesCache> cache,
-                                                  bool softwareRendering)
+                                                  bool softwareRendering,
+                                                  bool linearInterpolation)
   {
     boost::shared_ptr<ViewerViewport> viewport(
-      new ViewerViewport(context, source, canvas, cache, softwareRendering));
+      new ViewerViewport(context, source, canvas, cache, softwareRendering, linearInterpolation));
 
     {
       std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context.Lock());
@@ -2772,6 +2777,9 @@
 
       viewport->Register<OrthancStone::AnnotationsSceneLayer::AnnotationRemovedMessage>(
         *viewport->stoneAnnotations_, &ViewerViewport::Handle);
+
+      viewport->Register<OrthancStone::AnnotationsSceneLayer::TextAnnotationRequiredMessage>(
+        *viewport->stoneAnnotations_, &ViewerViewport::Handle);
     }
 
     {
@@ -2790,8 +2798,6 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
 
-    flipX_ = false;
-    flipY_ = false;
     fitNextContent_ = true;
     cineRate_ = DEFAULT_CINE_RATE;
     inverted_ = false;
@@ -2799,7 +2805,28 @@
     OrthancStone::LinearAlgebra::AssignVector(synchronizationOffset_, 0, 0, 0);
 
     frames_.reset(frames);
-    cursor_.reset(new SeriesCursor(frames_->GetFramesCount()));
+    cursor_.reset(new SeriesCursor(frames_->GetFramesCount(), false));
+    
+    if (frames_->GetFramesCount() != 0)
+    {
+      const OrthancStone::DicomInstanceParameters& firstInstance = frames_->GetInstanceOfFrame(0);
+      std::string modality;
+      if (firstInstance.GetTags().LookupStringValue(modality, Orthanc::DICOM_TAG_MODALITY, false))
+      {
+        if (modality == "MR" ||
+            modality == "CT" ||
+            modality == "NM" ||
+            modality == "OPT" ||
+            modality == "PT" ||
+            modality == "RTDOSE" ||
+            modality == "XA")
+        {
+          // For series that might correspond to 3D images, use their
+          // central frame as the first frame to be displayed
+          cursor_.reset(new SeriesCursor(frames_->GetFramesCount(), true));
+        }
+      }
+    }
 
     LOG(INFO) << "Number of frames in series: " << frames_->GetFramesCount();
 
@@ -3102,14 +3129,42 @@
 
   void FlipX()
   {
-    flipX_ = !flipX_;
-    UpdateCurrentTextureParameters();
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().FlipViewportX(
+        lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }    
   }
 
   void FlipY()
   {
-    flipY_ = !flipY_;
-    UpdateCurrentTextureParameters();
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().FlipViewportY(
+        lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }
+  }
+
+  void RotateLeft()
+  {
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().RotateViewport(
+        -PI / 2.0, lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }    
+  }
+
+  void RotateRight()
+  {
+    {
+      std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+      lock->GetController().GetScene().RotateViewport(
+        PI / 2.0, lock->GetCompositor().GetCanvasWidth(), lock->GetCompositor().GetCanvasHeight());
+      lock->Invalidate();
+    }
   }
 
   void Invert()
@@ -3224,14 +3279,30 @@
               viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Circle);
               break;
               
-            case WebViewerAction_CreateSegment:
-              viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Segment);
+            case WebViewerAction_CreateLength:
+              viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Length);
               break;
 
             case WebViewerAction_RemoveMeasure:
               viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Remove);
               break;
 
+            case WebViewerAction_CreatePixelProbe:
+              viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_PixelProbe);
+              break;
+
+            case WebViewerAction_CreateEllipseProbe:
+              viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_EllipseProbe);
+              break;
+
+            case WebViewerAction_CreateRectangleProbe:
+              viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_RectangleProbe);
+              break;
+
+            case WebViewerAction_CreateTextAnnotation:
+              viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_TextAnnotation);
+              break;
+
             default:
               viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Edit);
               break;
@@ -3405,6 +3476,25 @@
   }
 
 
+  void SetLinearInterpolation(bool linearInterpolation)
+  {
+    if (linearInterpolation_ != linearInterpolation)
+    {
+      linearInterpolation_ = linearInterpolation;
+      Redraw();
+    }
+  }
+
+  
+  void AddTextAnnotation(const std::string& label,
+                         const OrthancStone::ScenePoint2D& pointedPosition,
+                         const OrthancStone::ScenePoint2D& labelPosition)
+  {
+    stoneAnnotations_->AddTextAnnotation(label, pointedPosition, labelPosition);
+    Redraw();
+  }
+
+
   bool GetCurrentFrame(std::string& sopInstanceUid /* out */,
                        unsigned int& frameNumber /* out */) const
   {
@@ -3700,6 +3790,27 @@
       },
       viewport.GetCanvasId().c_str());
   }
+
+  virtual void SignalStoneTextAnnotationRequired(const ViewerViewport& viewport,
+                                                 const OrthancStone::ScenePoint2D& pointedPosition,
+                                                 const OrthancStone::ScenePoint2D& labelPosition) ORTHANC_OVERRIDE
+  {
+    EM_ASM({
+        const customEvent = document.createEvent("CustomEvent");
+        customEvent.initCustomEvent("TextAnnotationRequired", false, false,
+                                    { "canvasId" : UTF8ToString($0),
+                                      "pointedX" : $1,
+                                      "pointedY" : $2,
+                                      "labelX" : $3,
+                                      "labelY" : $4 });
+        window.dispatchEvent(customEvent);
+      },
+      viewport.GetCanvasId().c_str(),
+      pointedPosition.GetX(),
+      pointedPosition.GetY(),
+      labelPosition.GetX(),
+      labelPosition.GetY() );
+  }
 };
 
 
@@ -3709,6 +3820,7 @@
 static boost::shared_ptr<OrthancStone::WebAssemblyLoadersContext> context_;
 static std::string stringBuffer_;
 static bool softwareRendering_ = false;
+static bool linearInterpolation_ = true;
 static WebViewerAction leftButtonAction_ = WebViewerAction_Windowing;
 static WebViewerAction middleButtonAction_ = WebViewerAction_Pan;
 static WebViewerAction rightButtonAction_ = WebViewerAction_Zoom;
@@ -3755,7 +3867,7 @@
   if (found == allViewports_.end())
   {
     boost::shared_ptr<ViewerViewport> viewport(
-      ViewerViewport::Create(*context_, source_, canvas, framesCache_, softwareRendering_));
+      ViewerViewport::Create(*context_, source_, canvas, framesCache_, softwareRendering_, linearInterpolation_));
     viewport->SetMouseButtonActions(leftButtonAction_, middleButtonAction_, rightButtonAction_);
     viewport->AcquireObserver(new WebAssemblyObserver);
     viewport->SetOsiriXAnnotations(osiriXAnnotations_);
@@ -4494,6 +4606,28 @@
   
 
   EMSCRIPTEN_KEEPALIVE
+  void RotateLeft(const char* canvas)
+  {
+    try
+    {
+      GetViewport(canvas)->RotateLeft();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void RotateRight(const char* canvas)
+  {
+    try
+    {
+      GetViewport(canvas)->RotateRight();
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+  
+
+  EMSCRIPTEN_KEEPALIVE
   void SetSoftwareRendering(int softwareRendering)
   {
     softwareRendering_ = softwareRendering;
@@ -4508,6 +4642,23 @@
 
 
   EMSCRIPTEN_KEEPALIVE
+  void SetLinearInterpolation(int linearInterpolation)
+  {
+    linearInterpolation_ = linearInterpolation;
+
+    try
+    {
+      for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        it->second->SetLinearInterpolation(linearInterpolation);
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }  
+
+
+  EMSCRIPTEN_KEEPALIVE
   void SetMouseButtonActions(int leftAction,
                              int middleAction,
                              int rightAction)
@@ -4718,4 +4869,21 @@
     EXTERN_CATCH_EXCEPTIONS;
     return false;
   }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void AddTextAnnotation(const char* canvas,
+                         const char* label,
+                         double pointedX,
+                         double pointedY,
+                         double labelX,
+                         double labelY)
+  {
+    try
+    {
+      GetViewport(canvas)->AddTextAnnotation(label, OrthancStone::ScenePoint2D(pointedX, pointedY),
+                                             OrthancStone::ScenePoint2D(labelX, labelY));
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
 }