view Applications/Samples/SimpleViewerApplication.h @ 299:3897f9f28cfa am-callable-and-promise

backup work in progress: updated messaging framework with ICallable
author am@osimis.io
date Fri, 14 Sep 2018 16:44:01 +0200
parents 8c8da145fefa
children b4abaeb783b1
line wrap: on
line source

/**
 * 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/IStoneApplicationToWebApplicationAdapter.h"
#include "../../Platforms/Wasm/Defaults.h"
#endif
#include <Core/Logging.h>

namespace OrthancStone
{
  namespace Samples
  {
    class SimpleViewerApplication :
        public SampleApplicationBase,
#if ORTHANC_ENABLE_WASM==1
        public IStoneApplicationToWebApplicationAdapter,
#endif
        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;
          }
        }
      };

      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_);

          // sources
          smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService()));
          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");

        orthancApiClient_.reset(new OrthancApiClient(broker_, context_->GetWebService()));

        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::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived));
        }
        else
        {
          SelectStudy(parameters["studyId"].as<std::string>());
        }
      }

      void OnStudyListReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message)
      {
        const Json::Value& response = message.response_;

        if (response.isArray() && response.size() > 1)
        {
          SelectStudy(response[0].asString());
        }
      }
      void OnStudyReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& 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::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived));
          }
        }
      }

      void OnSeriesReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& 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)
          {
            mainWidget_->AddLayer(smartLoader_->GetFrame(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(broker_, "thumbnail-series-" + seriesId);
        thumbnails_.push_back(thumbnailWidget);
        thumbnailsLayout_->AddWidget(thumbnailWidget);
        thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, LayerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
        thumbnailWidget->AddLayer(smartLoader_->GetFrame(instanceId, 0));
        thumbnailWidget->SetInteractor(*thumbnailInteractor_);
      }

      void SelectStudy(const std::string& studyId)
      {
        orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
      }

      void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message)
      {
        message.origin_.SetDefaultView();
      }

      void SelectSeriesInMainViewport(const std::string& seriesId)
      {
        mainWidget_->ReplaceLayer(0, smartLoader_->GetFrame(instancesIdsPerSeriesId_[seriesId][0], 0));
#if ORTHANC_ENABLE_WASM==1
        NotifyStatusUpdateFromCppToWeb("series-description=" + seriesTags_[seriesId]["MainDicomTags"]["SeriesDescription"].asString());
#endif
      }

      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 HandleMessageFromWeb(std::string& output, const std::string& input) {
        if (input == "select-tool:line-measure")
        {
          currentTool_ = Tools_LineMeasure;
          NotifyStatusUpdateFromCppToWeb("currentTool=line-measure");
        }
        else if (input == "select-tool:circle-measure")
        {
          currentTool_ = Tools_CircleMeasure;
          NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure");
        }

        output = "ok";
      }

      virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) {
        UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
      }

      virtual void InitializeWasm() {

        AttachWidgetToWasmViewport("canvas", thumbnailsLayout_);
        AttachWidgetToWasmViewport("canvas2", mainWidget_);
      }
#endif
    };


  }
}