changeset 270:2d64f4d39610 am-2

backup (work in progress)
author am@osimis.io
date Thu, 23 Aug 2018 14:45:04 +0200
parents 0dfa83535cd7
children 46c5296d867e
files Applications/Samples/SimpleViewerApplication.h Framework/Messages/MessageType.h Framework/SmartLoader.cpp Framework/SmartLoader.h Framework/Toolbox/OrthancApiClient.cpp Framework/Toolbox/OrthancApiClient.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 7 files changed, 438 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SimpleViewerApplication.h	Wed Aug 22 15:22:33 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Thu Aug 23 14:45:04 2018 +0200
@@ -122,82 +122,21 @@
       };
 
 
-      //      void OffsetSlice(int offset)
-      //      {
-      //        if (source_ != NULL)
-      //        {
-      //          int slice = static_cast<int>(slice_) + offset;
-
-      //          if (slice < 0)
-      //          {
-      //            slice = 0;
-      //          }
-
-      //          if (slice >= static_cast<int>(source_->GetSliceCount()))
-      //          {
-      //            slice = source_->GetSliceCount() - 1;
-      //          }
-
-      //          if (slice != static_cast<int>(slice_))
-      //          {
-      //            SetSlice(slice);
-      //          }
-      //        }
-      //      }
-      
-
-      //      void SetSlice(size_t index)
-      //      {
-      //        if (source_ != NULL &&
-      //            index < source_->GetSliceCount())
-      //        {
-      //          slice_ = index;
-
-      //#if 1
-      //          widget_->SetSlice(source_->GetSlice(slice_).GetGeometry());
-      //#else
-      //          // TEST for scene extents - Rotate the axes
-      //          double a = 15.0 / 180.0 * M_PI;
-
-      //#if 1
-      //          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
-      //          Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0);
-      //#else
-      //          // Flip the normal
-      //          Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
-      //          Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0);
-      //#endif
-
-      //          SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y);
-      //          widget_->SetSlice(s);
-      //#endif
-      //        }
-      //      }
-
-
-      virtual void HandleMessage(IObservable& from, const IMessage& message) {
-        switch (message.GetType()) {
-        case MessageType_Widget_GeometryChanged:
-          dynamic_cast<LayerWidget&>(from).SetDefaultView();
-          break;
-        default:
-          VLOG("unhandled message type" << message.GetType());
-        }
-      }
-
       std::unique_ptr<Interactor>     interactor_;
       LayoutWidget*                   mainLayout_;
       LayoutWidget*                   thumbnailsLayout_;
       LayerWidget*                    mainViewport_;
       std::vector<LayerWidget*>       thumbnails_;
       std::vector<std::string>        instances_;
+
       unsigned int                    currentInstanceIndex_;
-      OrthancStone::WidgetViewport*                wasmViewport1_;
-      OrthancStone::WidgetViewport*                wasmViewport2_;
+      OrthancStone::WidgetViewport*   wasmViewport1_;
+      OrthancStone::WidgetViewport*   wasmViewport2_;
 
       IStatusBar*                     statusBar_;
       unsigned int                    slice_;
       std::unique_ptr<SmartLoader>    smartLoader_;
+      std::unique_ptr<OrthancApiClient>      orthancApiClient_;
 
     public:
       SimpleViewerApplication(MessageBroker& broker) :
@@ -210,6 +149,10 @@
       {
         DeclareIgnoredMessage(MessageType_Widget_ContentChanged);
         DeclareHandledMessage(MessageType_Widget_GeometryChanged);
+
+        DeclareHandledMessage(MessageType_OrthancApi_GetStudyIds_Ready);
+        DeclareHandledMessage(MessageType_OrthancApi_GetStudy_Ready);
+        DeclareHandledMessage(MessageType_OrthancApi_GetSeries_Ready);
       }
 
       virtual void Finalize() {}
@@ -221,10 +164,8 @@
         generic.add_options()
             //          ("study", boost::program_options::value<std::string>(),
             //           "Orthanc ID of the study")
-            ("instance1", boost::program_options::value<std::string>(),
-             "Orthanc ID of the instances")
-            ("instance2", boost::program_options::value<std::string>(),
-             "Orthanc ID of the instances")
+            ("studyId", boost::program_options::value<std::string>(),
+             "Orthanc ID of the study")
             ;
 
         options.add(generic);
@@ -238,58 +179,123 @@
 
         context_ = context;
         statusBar_ = &statusBar;
+
+        {// initialize viewports and layout
+          mainLayout_ = new LayoutWidget();
+          mainLayout_->SetPadding(10);
+          mainLayout_->SetBackgroundCleared(true);
+          mainLayout_->SetBackgroundColor(0, 0, 0);
+          mainLayout_->SetHorizontal();
+
+          thumbnailsLayout_ = new LayoutWidget();
+          thumbnailsLayout_->SetPadding(10);
+          thumbnailsLayout_->SetBackgroundCleared(true);
+          thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
+          thumbnailsLayout_->SetVertical();
+
+          mainViewport_ = new LayerWidget(broker_);
+          mainViewport_->RegisterObserver(*this);
+
+          // hierarchy
+          mainLayout_->AddWidget(thumbnailsLayout_);
+          mainLayout_->AddWidget(mainViewport_);
+
+          // sources
+          smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService()));
+          smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
+
+//          mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0));
+//          thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0));
+//          thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0));
+
+          mainLayout_->SetTransmitMouseOver(true);
+          interactor_.reset(new Interactor(*this));
+          mainViewport_->SetInteractor(*interactor_);
+        }
+
         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("instance1") < 1)
+        orthancApiClient_.reset(new OrthancApiClient(broker_, context_->GetWebService()));
+
+        if (parameters.count("studyId") < 1)
         {
-          LOG(ERROR) << "The instance ID is missing";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
+          orthancApiClient_->ScheduleGetStudyIds(*this);
         }
-        if (parameters.count("instance2") < 1)
+        else
         {
-          LOG(ERROR) << "The instance ID is missing";
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          SelectStudy(parameters["studyId"].as<std::string>());
         }
-        instances_.push_back(parameters["instance1"].as<std::string>());
-        instances_.push_back(parameters["instance2"].as<std::string>());
+      }
 
-        mainLayout_ = new LayoutWidget();
-        mainLayout_->SetPadding(10);
-        mainLayout_->SetBackgroundCleared(true);
-        mainLayout_->SetBackgroundColor(0, 0, 0);
-        mainLayout_->SetHorizontal();
-
-        thumbnailsLayout_ = new LayoutWidget();
-        thumbnailsLayout_->SetPadding(10);
-        thumbnailsLayout_->SetBackgroundCleared(true);
-        thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
-        thumbnailsLayout_->SetVertical();
+      void OnStudyListReceived(const Json::Value& response)
+      {
+        if (response.isArray() && response.size() > 1)
+        {
+          SelectStudy(response[0].asString());
+        }
+      }
+      void OnStudyReceived(const Json::Value& response)
+      {
+        if (response.isObject() && response["Series"].isArray())
+        {
+          for (size_t i=0; i < response["Series"].size(); i++)
+          {
+            orthancApiClient_->ScheduleGetSeries(*this, response["Series"][(int)i].asString());
+          }
+        }
+      }
 
-        mainViewport_ = new LayerWidget(broker_);
-        thumbnails_.push_back(new LayerWidget(broker_));
-        thumbnails_.push_back(new LayerWidget(broker_));
-        mainViewport_->RegisterObserver(*this);
-        thumbnails_[0]->RegisterObserver(*this);
-        thumbnails_[1]->RegisterObserver(*this);
+      void OnSeriesReceived(const Json::Value& response)
+      {
+        if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0)
+        {
+          LoadThumbnailForSeries(response["ID"].asString(), response["Instances"][0].asString());
+        }
+        //TODO: create layout and start loading frames
+        //mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0));
+        //thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0));
+        //thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0));
+      }
+
+      void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
+      {
+        LayerWidget* thumbnailWidget = new LayerWidget(broker_);
+        thumbnailWidget->RegisterObserver(*this);
+        thumbnailWidget->AddLayer(smartLoader_->GetFrame(instanceId, 0));
+        thumbnailsLayout_->AddWidget(thumbnailWidget);
+        thumbnails_.push_back(thumbnailWidget);
+      }
 
-        // hierarchy
-        mainLayout_->AddWidget(thumbnailsLayout_);
-        mainLayout_->AddWidget(mainViewport_);
-        thumbnailsLayout_->AddWidget(thumbnails_[0]);
-        thumbnailsLayout_->AddWidget(thumbnails_[1]);
+      void SelectStudy(const std::string& studyId)
+      {
+        orthancApiClient_->ScheduleGetStudy(*this, studyId);
+      }
+
+      void LoadSeries(const std::vector<std::string>& seriesIds)
+      {
+//        instances_.push_back(parameters["studyId"].as<std::string>());
+//        instances_.push_back(parameters["instance2"].as<std::string>());
+      }
 
-        // sources
-        smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService()));
-        smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
-
-        mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0));
-        thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0));
-        thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0));
-
-        mainLayout_->SetTransmitMouseOver(true);
-        interactor_.reset(new Interactor(*this));
-        mainViewport_->SetInteractor(*interactor_);
+      virtual void HandleMessage(IObservable& from, const IMessage& message) {
+        switch (message.GetType()) {
+        case MessageType_Widget_GeometryChanged:
+          dynamic_cast<LayerWidget&>(from).SetDefaultView();
+          break;
+        case MessageType_OrthancApi_GetStudyIds_Ready:
+          OnStudyListReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
+          break;
+        case MessageType_OrthancApi_GetSeries_Ready:
+          OnSeriesReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
+          break;
+        case MessageType_OrthancApi_GetStudy_Ready:
+          OnStudyReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
+          break;
+        default:
+          VLOG("unhandled message type" << message.GetType());
+        }
       }
 
 #if ORTHANC_ENABLE_SDL==0
--- a/Framework/Messages/MessageType.h	Wed Aug 22 15:22:33 2018 +0200
+++ b/Framework/Messages/MessageType.h	Thu Aug 23 14:45:04 2018 +0200
@@ -41,6 +41,12 @@
     MessageType_HttpRequestSuccess,
     MessageType_HttpRequestError,
 
+    MessageType_OrthancApi_InternalGetJsonResponseReady,
+    MessageType_OrthancApi_InternalGetJsonResponseError,
+
+    MessageType_OrthancApi_GetStudyIds_Ready,
+    MessageType_OrthancApi_GetStudy_Ready,
+    MessageType_OrthancApi_GetSeries_Ready,
 
     // used in unit tests only
     MessageType_Test1,
--- a/Framework/SmartLoader.cpp	Wed Aug 22 15:22:33 2018 +0200
+++ b/Framework/SmartLoader.cpp	Thu Aug 23 14:45:04 2018 +0200
@@ -29,13 +29,17 @@
     IObservable(broker),
     IObserver(broker),
     imageQuality_(SliceImageQuality_FullPam),
-    webService_(webService)
+    webService_(webService),
+    orthancApiClient_(broker, webService)
   {
     DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
     DeclareHandledMessage(MessageType_LayerSource_LayerReady);
     DeclareIgnoredMessage(MessageType_LayerSource_GeometryError);
     DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged);
     DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged);
+
+//    DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
+//    DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
   }
 
   void SmartLoader::HandleMessage(IObservable& from, const IMessage& message)
@@ -51,6 +55,12 @@
       const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
       // TODO keep track of objects that have been loaded already
     }; break;
+//    case MessageType_OrthancApi_GetStudyIds_Ready:
+//    {
+
+//      const OrthancApiClient::GetJsonResponseReadyMessage& msg = dynamic_cast<OrthancApiClient::GetJsonResponseReadyMessage&>(message);
+
+//    }; break;
     default:
       VLOG("unhandled message type" << message.GetType());
     }
@@ -75,6 +85,10 @@
     return layerSource.release();
   }
 
+  void SmartLoader::LoadStudyList()
+  {
+//    orthancApiClient_.ScheduleGetJsonRequest("/studies");
+  }
 
   void PreloadStudy(const std::string studyId)
   {
--- a/Framework/SmartLoader.h	Wed Aug 22 15:22:33 2018 +0200
+++ b/Framework/SmartLoader.h	Thu Aug 23 14:45:04 2018 +0200
@@ -24,15 +24,17 @@
 #include "Layers/ILayerSource.h"
 #include "Messages/IObservable.h"
 #include "../Platforms/Generic/OracleWebService.h"
+#include "Toolbox/OrthancApiClient.h"
 
 namespace OrthancStone
 {
   class SmartLoader : public IObservable, IObserver
   {
-    SliceImageQuality imageQuality_;
-    IWebService& webService_;
+    SliceImageQuality     imageQuality_;
+    IWebService&          webService_;
+    OrthancApiClient      orthancApiClient_;
 
-
+    int studyListRequest_;
   public:
     SmartLoader(MessageBroker& broker, IWebService& webService);  // TODO: add maxPreloadStorageSizeInBytes
 
@@ -40,6 +42,7 @@
 
     void PreloadStudy(const std::string studyId);
     void PreloadSeries(const std::string seriesId);
+    void LoadStudyList();
 
     void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Thu Aug 23 14:45:04 2018 +0200
@@ -0,0 +1,204 @@
+/**
+ * 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/>.
+ **/
+
+#include "OrthancApiClient.h"
+
+#include "MessagingToolbox.h"
+#include <Core/OrthancException.h>
+
+namespace OrthancStone {
+
+  struct OrthancApiClient::InternalGetJsonResponseReadyMessage :
+      public IMessage
+  {
+    OrthancApiClient::BaseRequest*  request_;
+    Json::Value   response_;
+
+    InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest*  request,
+                             const Json::Value& response)
+      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady),
+        request_(request),
+        response_(response)
+    {
+    }
+
+  };
+
+  struct OrthancApiClient::InternalGetJsonResponseErrorMessage :
+      public IMessage
+  {
+    OrthancApiClient::BaseRequest*  request_;
+
+    InternalGetJsonResponseErrorMessage(OrthancApiClient::BaseRequest* request)
+      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseError),
+        request_(request)
+    {
+    }
+  };
+
+
+  // this class handles a single request to the OrthancApiClient.
+  // Once the response is ready, it will emit a message to the responseObserver
+  // the responseObserver must handle only that message (and not all messages from the OrthancApiClient)
+  class OrthancApiClient::BaseRequest:
+      public IObserver,
+      public IObservable,
+      public Orthanc::IDynamicObject
+  {
+  public:
+    std::string               uri_;
+    OrthancApiClient&         orthanc_;
+    MessageType               messageToEmitWhenResponseReady_;
+    OrthancApiClient::Mode    mode_;
+
+  public:
+    BaseRequest(
+        OrthancApiClient& orthanc,
+                   IObserver& responseObserver,
+                   const std::string& uri,
+                   MessageType messageToEmitWhenResponseReady,
+                   OrthancApiClient::Mode mode)
+      : IObserver(orthanc.broker_),
+        IObservable(orthanc.broker_),
+        uri_(uri),
+        orthanc_(orthanc),
+        messageToEmitWhenResponseReady_(messageToEmitWhenResponseReady),
+        mode_(mode)
+    {
+      // this object will emit only a single message, the one the final responseObserver is expecting
+      DeclareEmittableMessage(messageToEmitWhenResponseReady);
+
+      // this object is observing the OrthancApi so it must handle all messages
+      DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
+      DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
+
+      orthanc_.RegisterObserver(*this);
+      this->RegisterObserver(responseObserver);
+    }
+    virtual ~BaseRequest() {}
+
+    // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver
+    virtual void HandleMessage(IObservable& from, const IMessage& message)
+    {
+      switch (message.GetType())
+      {
+        case MessageType_OrthancApi_InternalGetJsonResponseReady:
+      {
+        const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast<const OrthancApiClient::InternalGetJsonResponseReadyMessage&>(message);
+        EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_));
+        orthanc_.ReleaseRequest(messageReceived.request_);
+      }; break;
+      default:
+        throw MessageNotDeclaredException(message.GetType());
+      }
+    }
+
+  };
+
+
+  class OrthancApiClient::WebCallback : public IWebService::ICallback
+  {
+  private:
+    OrthancApiClient&  that_;
+
+  public:
+    WebCallback(MessageBroker& broker, OrthancApiClient&  that) :
+      IWebService::ICallback(broker),
+      that_(that)
+    {
+    }
+
+    virtual void OnHttpRequestSuccess(const std::string& uri,
+                                      const void* answer,
+                                      size_t answerSize,
+                                      Orthanc::IDynamicObject* payload)
+    {
+      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
+
+      switch (request->mode_)
+      {
+      case OrthancApiClient::Mode_GetJson:
+      {
+        Json::Value response;
+        if (MessagingToolbox::ParseJson(response, answer, answerSize))
+        {
+          OrthancApiClient::InternalGetJsonResponseReadyMessage msg(request, response);
+          that_.EmitMessage(msg);
+        }
+        else
+        {
+          OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
+          that_.EmitMessage(msg);
+        }
+      };  break;
+
+      default:
+        that_.ReleaseRequest(request);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    virtual void OnHttpRequestError(const std::string& uri,
+                                    Orthanc::IDynamicObject* payload)
+    {
+      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
+
+      switch (request->mode_)
+      {
+      case OrthancApiClient::Mode_GetJson:
+      {
+        OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
+        that_.EmitMessage(msg);
+      };  break;
+
+      default:
+        that_.ReleaseRequest(request);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  };
+
+  OrthancApiClient::OrthancApiClient(MessageBroker &broker, IWebService &orthanc)
+    : IObservable(broker),
+      orthanc_(orthanc),
+      webCallback_(new OrthancApiClient::WebCallback(broker, *this))
+  {
+    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
+    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
+  }
+
+  void OrthancApiClient::ScheduleGetJsonRequest(IObserver &responseObserver, const std::string &uri, MessageType messageToEmitWhenResponseReady)
+  {
+    OrthancApiClient::BaseRequest* request = new OrthancApiClient::BaseRequest(*this,
+                                                                               responseObserver,
+                                                                               uri,
+                                                                               messageToEmitWhenResponseReady,
+                                                                               OrthancApiClient::Mode_GetJson);
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), request);
+    requestsInProgress_.insert(request);
+  }
+
+  void OrthancApiClient::ReleaseRequest(BaseRequest* request)
+  {
+    requestsInProgress_.erase(request);
+    delete request;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/OrthancApiClient.h	Thu Aug 23 14:45:04 2018 +0200
@@ -0,0 +1,90 @@
+/**
+ * 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 <boost/shared_ptr.hpp>
+#include <json/json.h>
+
+#include "IWebService.h"
+#include "../Messages/IObservable.h"
+
+
+namespace OrthancStone
+{
+  class OrthancApiClient:
+      public IObservable
+  {
+  protected:
+    class BaseRequest;
+    class GetJsonRequest;
+
+    struct InternalGetJsonResponseReadyMessage;
+    struct InternalGetJsonResponseErrorMessage;
+
+  public:
+    struct GetJsonResponseReadyMessage : public IMessage
+    {
+      Json::Value   response_;
+      std::string   uri_;
+
+      GetJsonResponseReadyMessage(MessageType messageType,
+                                  const std::string& uri,
+                                  const Json::Value& response)
+        : IMessage(messageType),
+          uri_(uri),
+          response_(response)
+      {
+      }
+    };
+
+  public:
+
+    enum Mode
+    {
+      Mode_GetJson
+    };
+
+  protected:
+    IWebService&                      orthanc_;
+    class WebCallback;
+    boost::shared_ptr<WebCallback>    webCallback_;  // This is a PImpl pattern
+    std::set<BaseRequest*>            requestsInProgress_;
+
+//    int ScheduleGetJsonRequest(const std::string& uri);
+
+    void ReleaseRequest(BaseRequest* request);
+
+  public:
+    OrthancApiClient(MessageBroker& broker,
+                        IWebService& orthanc);
+    virtual ~OrthancApiClient() {}
+
+    // schedule a GET request expecting a JSON request.
+    // once the response is ready, it will emit a OrthancApiClient::GetJsonResponseReadyMessage message whose messageType is specified in the call
+    void ScheduleGetJsonRequest(IObserver& responseObserver, const std::string& uri, MessageType messageToEmitWhenResponseReady);
+
+    void ScheduleGetStudyIds(IObserver& responseObserver) {ScheduleGetJsonRequest(responseObserver, "/studies", MessageType_OrthancApi_GetStudyIds_Ready);}
+    void ScheduleGetStudy(IObserver& responseObserver, const std::string& studyId) {ScheduleGetJsonRequest(responseObserver, "/studies/" + studyId, MessageType_OrthancApi_GetStudy_Ready);}
+    void ScheduleGetSeries(IObserver& responseObserver, const std::string& seriesId) {ScheduleGetJsonRequest(responseObserver, "/series/" + seriesId, MessageType_OrthancApi_GetSeries_Ready);}
+
+  };
+}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Wed Aug 22 15:22:33 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Aug 23 14:45:04 2018 +0200
@@ -209,6 +209,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrientedBoundingBox.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancSlicesLoader.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancApiClient.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp