diff Applications/Samples/SimpleViewerApplicationSingleFile.h @ 319:daa04d15192c am-2

new SimpleViewer sample that has been split in multiple files to be able to scale it
author am@osimis.io
date Thu, 11 Oct 2018 13:16:54 +0200
parents
children 612238b3f3e8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h	Thu Oct 11 13:16:54 2018 +0200
@@ -0,0 +1,424 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "SampleApplicationBase.h"
+
+#include "../../Framework/Layers/OrthancFrameLayerSource.h"
+#include "../../Framework/Layers/CircleMeasureTracker.h"
+#include "../../Framework/Layers/LineMeasureTracker.h"
+#include "../../Framework/Widgets/LayerWidget.h"
+#include "../../Framework/Widgets/LayoutWidget.h"
+#include "../../Framework/Messages/IObserver.h"
+#include "../../Framework/SmartLoader.h"
+
+#if ORTHANC_ENABLE_WASM==1
+#include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h"
+#include "../../Platforms/Wasm/Defaults.h"
+#endif
+
+#if ORTHANC_ENABLE_QT==1
+#include "Qt/SampleMainWindow.h"
+#endif
+#include <Core/Logging.h>
+
+namespace OrthancStone
+{
+  namespace Samples
+  {
+    class SimpleViewerApplication :
+        public SampleApplicationBase,
+        public IObserver
+    {
+    private:
+      class ThumbnailInteractor : public IWorldSceneInteractor
+      {
+      private:
+        SimpleViewerApplication&  application_;
+      public:
+        ThumbnailInteractor(SimpleViewerApplication&  application) :
+          application_(application)
+        {
+        }
+
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            KeyboardModifiers modifiers,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          if (button == MouseButton_Left)
+          {
+            statusBar->SetMessage("selected thumbnail " + widget.GetName());
+            std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-"));
+            application_.SelectSeriesInMainViewport(seriesId);
+          }
+          return NULL;
+        }
+        virtual void MouseOver(CairoContext& context,
+                               WorldSceneWidget& widget,
+                               const ViewportGeometry& view,
+                               double x,
+                               double y,
+                               IStatusBar* statusBar)
+        {}
+
+        virtual void MouseWheel(WorldSceneWidget& widget,
+                                MouseWheelDirection direction,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {}
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {}
+
+      };
+
+      class MainWidgetInteractor : public IWorldSceneInteractor
+      {
+      private:
+        SimpleViewerApplication&  application_;
+        
+      public:
+        MainWidgetInteractor(SimpleViewerApplication&  application) :
+          application_(application)
+        {
+        }
+        
+        virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
+                                                            const ViewportGeometry& view,
+                                                            MouseButton button,
+                                                            KeyboardModifiers modifiers,
+                                                            double x,
+                                                            double y,
+                                                            IStatusBar* statusBar)
+        {
+          if (button == MouseButton_Left)
+          {
+            if (application_.currentTool_ == Tools_LineMeasure)
+            {
+              return new LineMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10);
+            }
+            else if (application_.currentTool_ == Tools_CircleMeasure)
+            {
+              return new CircleMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10);
+            }
+          }
+          return NULL;
+        }
+
+        virtual void MouseOver(CairoContext& context,
+                               WorldSceneWidget& widget,
+                               const ViewportGeometry& view,
+                               double x,
+                               double y,
+                               IStatusBar* statusBar)
+        {
+          if (statusBar != NULL)
+          {
+            Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
+            
+            char buf[64];
+            sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)",
+                    p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
+            statusBar->SetMessage(buf);
+          }
+        }
+
+        virtual void MouseWheel(WorldSceneWidget& widget,
+                                MouseWheelDirection direction,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+        }
+
+        virtual void KeyPressed(WorldSceneWidget& widget,
+                                char key,
+                                KeyboardModifiers modifiers,
+                                IStatusBar* statusBar)
+        {
+          switch (key)
+          {
+          case 's':
+            widget.SetDefaultView();
+            break;
+
+          default:
+            break;
+          }
+        }
+      };
+
+
+#if ORTHANC_ENABLE_WASM==1
+      class SimpleViewerApplicationAdapter : public WasmPlatformApplicationAdapter
+      {
+        SimpleViewerApplication&  viewerApplication_;
+
+      public:
+        SimpleViewerApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application)
+          : WasmPlatformApplicationAdapter(broker, application),
+          viewerApplication_(application)
+        {
+
+        }
+
+        virtual void HandleMessageFromWeb(std::string& output, const std::string& input) {
+          if (input == "select-tool:line-measure")
+          {
+            viewerApplication_.currentTool_ = Tools_LineMeasure;
+            NotifyStatusUpdateFromCppToWeb("currentTool=line-measure");
+          }
+          else if (input == "select-tool:circle-measure")
+          {
+            viewerApplication_.currentTool_ = Tools_CircleMeasure;
+            NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure");
+          }
+
+          output = "ok";
+        }
+
+        virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) {
+          UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
+        }
+
+      };
+#endif
+      enum Tools {
+        Tools_LineMeasure,
+        Tools_CircleMeasure
+      };
+
+      Tools                           currentTool_;
+      std::unique_ptr<MainWidgetInteractor> mainWidgetInteractor_;
+      std::unique_ptr<ThumbnailInteractor>  thumbnailInteractor_;
+      LayoutWidget*                   mainLayout_;
+      LayoutWidget*                   thumbnailsLayout_;
+      LayerWidget*                    mainWidget_;
+      std::vector<LayerWidget*>       thumbnails_;
+      std::map<std::string, std::vector<std::string>> instancesIdsPerSeriesId_;
+      std::map<std::string, Json::Value> seriesTags_;
+
+      unsigned int                    currentInstanceIndex_;
+      OrthancStone::WidgetViewport*   wasmViewport1_;
+      OrthancStone::WidgetViewport*   wasmViewport2_;
+
+      IStatusBar*                     statusBar_;
+      std::unique_ptr<SmartLoader>    smartLoader_;
+      std::unique_ptr<OrthancApiClient>      orthancApiClient_;
+
+    public:
+      SimpleViewerApplication(MessageBroker& broker) :
+        IObserver(broker),
+        currentTool_(Tools_LineMeasure),
+        mainLayout_(NULL),
+        currentInstanceIndex_(0),
+        wasmViewport1_(NULL),
+        wasmViewport2_(NULL)
+      {
+//        DeclareIgnoredMessage(MessageType_Widget_ContentChanged);
+      }
+
+      virtual void Finalize() {}
+      virtual IWidget* GetCentralWidget() {return mainLayout_;}
+
+      virtual void DeclareStartupOptions(boost::program_options::options_description& options)
+      {
+        boost::program_options::options_description generic("Sample options");
+        generic.add_options()
+            ("studyId", boost::program_options::value<std::string>(),
+             "Orthanc ID of the study")
+            ;
+
+        options.add(generic);
+      }
+
+      virtual void Initialize(StoneApplicationContext* context,
+                              IStatusBar& statusBar,
+                              const boost::program_options::variables_map& parameters)
+      {
+        using namespace OrthancStone;
+
+        context_ = context;
+        statusBar_ = &statusBar;
+
+        {// initialize viewports and layout
+          mainLayout_ = new LayoutWidget("main-layout");
+          mainLayout_->SetPadding(10);
+          mainLayout_->SetBackgroundCleared(true);
+          mainLayout_->SetBackgroundColor(0, 0, 0);
+          mainLayout_->SetHorizontal();
+
+          thumbnailsLayout_ = new LayoutWidget("thumbnail-layout");
+          thumbnailsLayout_->SetPadding(10);
+          thumbnailsLayout_->SetBackgroundCleared(true);
+          thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
+          thumbnailsLayout_->SetVertical();
+
+          mainWidget_ = new LayerWidget(broker_, "main-viewport");
+          //mainWidget_->RegisterObserver(*this);
+
+          // hierarchy
+          mainLayout_->AddWidget(thumbnailsLayout_);
+          mainLayout_->AddWidget(mainWidget_);
+
+          orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService()));
+
+          // sources
+          smartLoader_.reset(new SmartLoader(IObserver::broker_, *orthancApiClient_));
+          smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
+
+          mainLayout_->SetTransmitMouseOver(true);
+          mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
+          mainWidget_->SetInteractor(*mainWidgetInteractor_);
+          thumbnailInteractor_.reset(new ThumbnailInteractor(*this));
+        }
+
+        statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
+        statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport");
+
+
+        if (parameters.count("studyId") < 1)
+        {
+          LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
+          orthancApiClient_->GetJsonAsync("/studies", new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived));
+        }
+        else
+        {
+          SelectStudy(parameters["studyId"].as<std::string>());
+        }
+      }
+
+      void OnStudyListReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
+      {
+        const Json::Value& response = message.Response;
+
+        if (response.isArray() && response.size() > 1)
+        {
+          SelectStudy(response[0].asString());
+        }
+      }
+      void OnStudyReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
+      {
+        const Json::Value& response = message.Response;
+
+        if (response.isObject() && response["Series"].isArray())
+        {
+          for (size_t i=0; i < response["Series"].size(); i++)
+          {
+            orthancApiClient_->GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived));
+          }
+        }
+      }
+
+      void OnSeriesReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
+      {
+        const Json::Value& response = message.Response;
+
+        if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0)
+        {
+          // keep track of all instances IDs
+          const std::string& seriesId = response["ID"].asString();
+          seriesTags_[seriesId] = response;
+          instancesIdsPerSeriesId_[seriesId] = std::vector<std::string>();
+          for (size_t i = 0; i < response["Instances"].size(); i++)
+          {
+            const std::string& instanceId = response["Instances"][static_cast<int>(i)].asString();
+            instancesIdsPerSeriesId_[seriesId].push_back(instanceId);
+          }
+
+          // load the first instance in the thumbnail
+          LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]);
+
+          // if this is the first thumbnail loaded, load the first instance in the mainWidget
+          if (mainWidget_->GetLayerCount() == 0)
+          {
+            smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
+          }
+        }
+      }
+
+      void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
+      {
+        LOG(INFO) << "Loading thumbnail for series " << seriesId;
+        LayerWidget* thumbnailWidget = new LayerWidget(IObserver::broker_, "thumbnail-series-" + seriesId);
+        thumbnails_.push_back(thumbnailWidget);
+        thumbnailsLayout_->AddWidget(thumbnailWidget);
+        thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, LayerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
+        smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0);
+        thumbnailWidget->SetInteractor(*thumbnailInteractor_);
+      }
+
+      void SelectStudy(const std::string& studyId)
+      {
+        orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
+      }
+
+      void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message)
+      {
+        message.origin_.SetDefaultView();
+      }
+
+      void SelectSeriesInMainViewport(const std::string& seriesId)
+      {
+        smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
+      }
+
+      virtual void OnPushButton1Clicked() {}
+      virtual void OnPushButton2Clicked() {}
+      virtual void OnTool1Clicked() { currentTool_ = Tools_LineMeasure;}
+      virtual void OnTool2Clicked() { currentTool_ = Tools_CircleMeasure;}
+
+      virtual void GetButtonNames(std::string& pushButton1,
+                                  std::string& pushButton2,
+                                  std::string& tool1,
+                                  std::string& tool2
+                                  ) {
+        tool1 = "line";
+        tool2 = "circle";
+        pushButton1 = "action1";
+        pushButton2 = "action2";
+      }
+
+#if ORTHANC_ENABLE_WASM==1
+      virtual void InitializeWasm() {
+
+        AttachWidgetToWasmViewport("canvas", thumbnailsLayout_);
+        AttachWidgetToWasmViewport("canvas2", mainWidget_);
+      }
+#endif
+
+#if ORTHANC_ENABLE_QT==1
+      virtual QStoneMainWindow* CreateQtMainWindow() {
+        return new SampleMainWindow(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this);
+      }
+#endif
+    };
+
+
+  }
+}