diff Samples/Sdl/Loader.cpp @ 646:b4fe9642e83b

Merge from default
author Benjamin Golinvaux <bgo@osimis.io>
date Mon, 13 May 2019 15:22:08 +0200
parents 1e9ed656318e f0008c55e5f7
children 6af3099ed8da
line wrap: on
line diff
--- a/Samples/Sdl/Loader.cpp	Mon May 13 15:12:56 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Mon May 13 15:22:08 2019 +0200
@@ -28,17 +28,23 @@
 #include "../../Framework/Volumes/ImageBuffer3D.h"
 
 // From Orthanc framework
+#include <Core/Compression/GzipCompressor.h>
+#include <Core/Compression/ZlibCompressor.h>
 #include <Core/DicomFormat/DicomArray.h>
 #include <Core/DicomFormat/DicomImageInformation.h>
 #include <Core/HttpClient.h>
 #include <Core/IDynamicObject.h>
 #include <Core/Images/Image.h>
 #include <Core/Images/ImageProcessing.h>
+#include <Core/Images/JpegReader.h>
+#include <Core/Images/PamReader.h>
+#include <Core/Images/PngReader.h>
 #include <Core/Images/PngWriter.h>
 #include <Core/Logging.h>
 #include <Core/MultiThreading/SharedMessageQueue.h>
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
+#include <Core/SystemToolbox.h>
 
 #include <json/reader.h>
 #include <json/value.h>
@@ -56,7 +62,9 @@
   public:
     enum Type
     {
-      Type_OrthancApi
+      Type_OrthancRestApi,
+      Type_GetOrthancImage,
+      Type_GetOrthancWebViewerJpeg
     };
 
     virtual ~IOracleCommand()
@@ -131,20 +139,56 @@
 
 
 
+  class OracleCommandExceptionMessage : public OrthancStone::IMessage
+  {
+    ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+
+  private:
+    const IOracleCommand&       command_;
+    Orthanc::OrthancException   exception_;
+
+  public:
+    OracleCommandExceptionMessage(const IOracleCommand& command,
+                                  const Orthanc::OrthancException& exception) :
+      command_(command),
+      exception_(exception)
+    {
+    }
+
+    OracleCommandExceptionMessage(const IOracleCommand& command,
+                                  const Orthanc::ErrorCode& error) :
+      command_(command),
+      exception_(error)
+    {
+    }
+
+    const IOracleCommand& GetCommand() const
+    {
+      return command_;
+    }
+    
+    const Orthanc::OrthancException& GetException() const
+    {
+      return exception_;
+    }
+  };
+  
+
   typedef std::map<std::string, std::string>  HttpHeaders;
 
-  class OrthancApiOracleCommand : public OracleCommandWithPayload
+  class OrthancRestApiCommand : public OracleCommandWithPayload
   {
   public:
-    class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess,   // TODO
-                                                              OrthancApiOracleCommand>
+    class SuccessMessage : public OrthancStone::OriginMessage<OrthancRestApiCommand>
     {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
     private:
       HttpHeaders   headers_;
       std::string   answer_;
 
     public:
-      SuccessMessage(const OrthancApiOracleCommand& command,
+      SuccessMessage(const OrthancRestApiCommand& command,
                      const HttpHeaders& answerHeaders,
                      std::string& answer  /* will be swapped to avoid a memcpy() */) :
         OriginMessage(command),
@@ -174,27 +218,6 @@
     };
 
 
-    class FailureMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestError,   // TODO
-                                                              OrthancApiOracleCommand>
-    {
-    private:
-      Orthanc::HttpStatus  status_;
-
-    public:
-      FailureMessage(const OrthancApiOracleCommand& command,
-                     Orthanc::HttpStatus status) :
-        OriginMessage(command),
-        status_(status)
-      {
-      }
-
-      Orthanc::HttpStatus GetHttpStatus() const
-      {
-        return status_;
-      }
-    };
-
-
   private:
     Orthanc::HttpMethod  method_;
     std::string          uri_;
@@ -203,10 +226,10 @@
     unsigned int         timeout_;
 
     std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
-    std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> >  failureCallback_;
+    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
 
   public:
-    OrthancApiOracleCommand() :
+    OrthancRestApiCommand() :
       method_(Orthanc::HttpMethod_Get),
       uri_("/"),
       timeout_(10)
@@ -215,7 +238,7 @@
 
     virtual Type GetType() const
     {
-      return Type_OrthancApi;
+      return Type_OrthancRestApi;
     }
 
     void SetMethod(Orthanc::HttpMethod method)
@@ -239,6 +262,11 @@
       body_ = writer.write(json);
     }
 
+    void SetHttpHeaders(const HttpHeaders& headers)
+    {
+      headers_ = headers;
+    }
+
     void SetHttpHeader(const std::string& key,
                        const std::string& value)
     {
@@ -286,6 +314,425 @@
 
 
 
+
+  class GetOrthancImageCommand : public OracleCommandWithPayload
+  {
+  public:
+    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancImageCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+      Orthanc::MimeType                      mime_;
+
+    public:
+      SuccessMessage(const GetOrthancImageCommand& command,
+                     Orthanc::ImageAccessor* image,   // Takes ownership
+                     Orthanc::MimeType mime) :
+        OriginMessage(command),
+        image_(image),
+        mime_(mime)
+      {
+        if (image == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+      }
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return *image_;
+      }
+
+      Orthanc::MimeType GetMimeType() const
+      {
+        return mime_;
+      }
+    };
+
+
+  private:
+    std::string    uri_;
+    HttpHeaders    headers_;
+    unsigned int   timeout_;
+
+    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
+    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
+
+  public:
+    GetOrthancImageCommand() :
+      uri_("/"),
+      timeout_(10)
+    {
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancImage;
+    }
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = uri;
+    }
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    const std::string& GetUri() const
+    {
+      return uri_;
+    }
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void ProcessHttpAnswer(IMessageEmitter& emitter,
+                           const OrthancStone::IObserver& receiver,
+                           const std::string& answer,
+                           const HttpHeaders& answerHeaders) const
+    {
+      Orthanc::MimeType contentType = Orthanc::MimeType_Binary;
+
+      for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
+           it != answerHeaders.end(); ++it)
+      {
+        std::string s;
+        Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+        if (s == "content-type")
+        {
+          contentType = Orthanc::StringToMimeType(it->second);
+          break;
+        }
+      }
+
+      std::auto_ptr<Orthanc::ImageAccessor> image;
+
+      switch (contentType)
+      {
+        case Orthanc::MimeType_Png:
+        {
+          image.reset(new Orthanc::PngReader);
+          dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer);
+          break;
+        }
+
+        case Orthanc::MimeType_Pam:
+        {
+          image.reset(new Orthanc::PamReader);
+          dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer);
+          break;
+        }
+
+        case Orthanc::MimeType_Jpeg:
+        {
+          image.reset(new Orthanc::JpegReader);
+          dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer);
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                          "Unsupported HTTP Content-Type for an image: " + 
+                                          std::string(Orthanc::EnumerationToString(contentType)));
+      }
+
+      SuccessMessage message(*this, image.release(), contentType);
+      emitter.EmitMessage(receiver, message);
+    }
+  };
+
+
+
+  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
+  {
+  public:
+    class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancWebViewerJpegCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+
+    public:
+      SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
+                     Orthanc::ImageAccessor* image) :   // Takes ownership
+        OriginMessage(command),
+        image_(image)
+      {
+        if (image == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+      }
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return *image_;
+      }
+    };
+
+  private:
+    std::string           instanceId_;
+    unsigned int          frame_;
+    unsigned int          quality_;
+    HttpHeaders           headers_;
+    unsigned int          timeout_;
+    Orthanc::PixelFormat  expectedFormat_;
+
+    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
+    std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> >  failureCallback_;
+
+  public:
+    GetOrthancWebViewerJpegCommand() :
+      frame_(0),
+      quality_(95),
+      timeout_(10),
+      expectedFormat_(Orthanc::PixelFormat_Grayscale8)
+    {
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancWebViewerJpeg;
+    }
+
+    void SetExpectedFormat(Orthanc::PixelFormat format)
+    {
+      expectedFormat_ = format;
+    }
+
+    void SetInstance(const std::string& instanceId)
+    {
+      instanceId_ = instanceId;
+    }
+
+    void SetFrame(unsigned int frame)
+    {
+      frame_ = frame;
+    }
+
+    void SetQuality(unsigned int quality)
+    {
+      if (quality <= 0 ||
+          quality > 100)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        quality_ = quality;
+      }
+    }
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    Orthanc::PixelFormat GetExpectedFormat() const
+    {
+      return expectedFormat_;
+    }
+
+    const std::string& GetInstanceId() const
+    {
+      return instanceId_;
+    }
+
+    unsigned int GetFrame() const
+    {
+      return frame_;
+    }
+
+    unsigned int GetQuality() const
+    {
+      return quality_;
+    }
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    std::string GetUri() const
+    {
+      return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
+              "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
+    }
+
+    void ProcessHttpAnswer(IMessageEmitter& emitter,
+                           const OrthancStone::IObserver& receiver,
+                           const std::string& answer) const
+    {
+      // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
+      
+      Json::Value encoded;
+
+      {
+        Json::Reader reader;
+        if (!reader.parse(answer, encoded))
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      }
+
+      if (encoded.type() != Json::objectValue ||
+          !encoded.isMember("Orthanc") ||
+          encoded["Orthanc"].type() != Json::objectValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      const Json::Value& info = encoded["Orthanc"];
+      if (!info.isMember("PixelData") ||
+          !info.isMember("Stretched") ||
+          !info.isMember("Compression") ||
+          info["Compression"].type() != Json::stringValue ||
+          info["PixelData"].type() != Json::stringValue ||
+          info["Stretched"].type() != Json::booleanValue ||
+          info["Compression"].asString() != "Jpeg")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      bool isSigned = false;
+      bool isStretched = info["Stretched"].asBool();
+    
+      if (info.isMember("IsSigned"))
+      {
+        if (info["IsSigned"].type() != Json::booleanValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          isSigned = info["IsSigned"].asBool();
+        }
+      }
+    
+      std::auto_ptr<Orthanc::ImageAccessor> reader;
+    
+      {
+        std::string jpeg;
+        Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
+      
+        reader.reset(new Orthanc::JpegReader);
+        dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
+      }
+    
+      if (reader->GetFormat() == Orthanc::PixelFormat_RGB24)  // This is a color image
+      {
+        if (expectedFormat_ != Orthanc::PixelFormat_RGB24)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      
+        if (isSigned || isStretched)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          SuccessMessage message(*this, reader.release());
+          emitter.EmitMessage(receiver, message);
+          return;
+        }
+      }
+    
+      if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      if (!isStretched)
+      {
+        if (expectedFormat_ != reader->GetFormat())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          SuccessMessage message(*this, reader.release());
+          emitter.EmitMessage(receiver, message);
+          return;
+        }
+      }
+    
+      int32_t stretchLow = 0;
+      int32_t stretchHigh = 0;
+    
+      if (!info.isMember("StretchLow") ||
+          !info.isMember("StretchHigh") ||
+          info["StretchLow"].type() != Json::intValue ||
+          info["StretchHigh"].type() != Json::intValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      stretchLow = info["StretchLow"].asInt();
+      stretchHigh = info["StretchHigh"].asInt();
+    
+      if (stretchLow < -32768 ||
+          stretchHigh > 65535 ||
+          (stretchLow < 0 && stretchHigh > 32767))
+      {
+        // This range cannot be represented with a uint16_t or an int16_t
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    
+      // Decode a grayscale JPEG 8bpp image coming from the Web viewer
+      std::auto_ptr<Orthanc::ImageAccessor> image
+        (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false));
+
+      Orthanc::ImageProcessing::Convert(*image, *reader);
+      reader.reset();
+    
+      float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
+    
+      if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
+      {
+        float offset = static_cast<float>(stretchLow) / scaling;
+        Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
+      }
+    
+      SuccessMessage message(*this, image.release());
+      emitter.EmitMessage(receiver, message);
+    }
+  };
+
+
+
+
+
   class NativeOracle : public IOracle
   {
   private:
@@ -336,53 +783,115 @@
     std::vector<boost::thread*>    workers_;
 
 
+    void CopyHttpHeaders(Orthanc::HttpClient& client,
+                         const HttpHeaders& headers)
+    {
+      for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
+      {
+        client.AddHeader(it->first, it->second);
+      }
+    }
+
+
+    void DecodeAnswer(std::string& answer,
+                      const HttpHeaders& headers)
+    {
+      Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
+
+      for (HttpHeaders::const_iterator it = headers.begin(); 
+           it != headers.end(); ++it)
+      {
+        std::string s;
+        Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+        if (s == "content-encoding")
+        {
+          if (it->second == "gzip")
+          {
+            contentEncoding = Orthanc::HttpCompression_Gzip;
+          }
+          else 
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                            "Unsupported HTTP Content-Encoding: " + it->second);
+          }
+
+          break;
+        }
+      }
+
+      if (contentEncoding == Orthanc::HttpCompression_Gzip)
+      {
+        std::string compressed;
+        answer.swap(compressed);
+          
+        Orthanc::GzipCompressor compressor;
+        compressor.Uncompress(answer, compressed.c_str(), compressed.size());
+      }
+    }
+
+
     void Execute(const OrthancStone::IObserver& receiver,
-                 const OrthancApiOracleCommand& command)
+                 const OrthancRestApiCommand& command)
     {
-      Orthanc::HttpClient  client(orthanc_, command.GetUri());
+      Orthanc::HttpClient client(orthanc_, command.GetUri());
       client.SetMethod(command.GetMethod());
       client.SetTimeout(command.GetTimeout());
 
+      CopyHttpHeaders(client, command.GetHttpHeaders());
+
       if (command.GetMethod() == Orthanc::HttpMethod_Post ||
           command.GetMethod() == Orthanc::HttpMethod_Put)
       {
         client.SetBody(command.GetBody());
       }
-      
-      {
-        const HttpHeaders& headers = command.GetHttpHeaders();
-        for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
-        {
-          client.AddHeader(it->first, it->second);
-        }
-      }
+
+      std::string answer;
+      HttpHeaders answerHeaders;
+      client.ApplyAndThrowException(answer, answerHeaders);
+
+      DecodeAnswer(answer, answerHeaders);
+
+      OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
+      emitter_.EmitMessage(receiver, message);
+    }
+
+
+    void Execute(const OrthancStone::IObserver& receiver,
+                 const GetOrthancImageCommand& command)
+    {
+      Orthanc::HttpClient client(orthanc_, command.GetUri());
+      client.SetTimeout(command.GetTimeout());
+
+      CopyHttpHeaders(client, command.GetHttpHeaders());
 
       std::string answer;
       HttpHeaders answerHeaders;
-
-      bool success;
-      try
-      {
-        success = client.Apply(answer, answerHeaders);
-      }
-      catch (Orthanc::OrthancException& e)
-      {
-        success = false;
-      }
+      client.ApplyAndThrowException(answer, answerHeaders);
 
-      if (success)
-      {
-        OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer);
-        emitter_.EmitMessage(receiver, message);
-      }
-      else
-      {
-        OrthancApiOracleCommand::FailureMessage message(command, client.GetLastStatus());
-        emitter_.EmitMessage(receiver, message);
-      }
+      DecodeAnswer(answer, answerHeaders);
+
+      command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders);
     }
 
 
+    void Execute(const OrthancStone::IObserver& receiver,
+                 const GetOrthancWebViewerJpegCommand& command)
+    {
+      Orthanc::HttpClient client(orthanc_, command.GetUri());
+      client.SetTimeout(command.GetTimeout());
+
+      CopyHttpHeaders(client, command.GetHttpHeaders());
+
+      std::string answer;
+      HttpHeaders answerHeaders;
+      client.ApplyAndThrowException(answer, answerHeaders);
+
+      DecodeAnswer(answer, answerHeaders);
+
+      command.ProcessHttpAnswer(emitter_, receiver, answer);
+    }
+
 
     void Step()
     {
@@ -390,15 +899,27 @@
 
       if (object.get() != NULL)
       {
+        printf("===========================> REQUEST\n");
+        
         const Item& item = dynamic_cast<Item&>(*object);
 
         try
         {
           switch (item.GetCommand().GetType())
           {
-            case IOracleCommand::Type_OrthancApi:
+            case IOracleCommand::Type_OrthancRestApi:
+              Execute(item.GetReceiver(), 
+                      dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand()));
+              break;
+
+            case IOracleCommand::Type_GetOrthancImage:
               Execute(item.GetReceiver(), 
-                      dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand()));
+                      dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand()));
+              break;
+
+            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+              Execute(item.GetReceiver(), 
+                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand()));
               break;
 
             default:
@@ -408,10 +929,13 @@
         catch (Orthanc::OrthancException& e)
         {
           LOG(ERROR) << "Exception within the oracle: " << e.What();
+          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e));
         }
         catch (...)
         {
           LOG(ERROR) << "Native exception within the oracle";
+          emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage
+                               (item.GetCommand(), Orthanc::ErrorCode_InternalError));
         }
       }
     }
@@ -562,8 +1086,15 @@
     virtual void EmitMessage(const OrthancStone::IObserver& observer,
                              const OrthancStone::IMessage& message)
     {
-      boost::unique_lock<boost::shared_mutex>  lock(mutex_);
-      oracleObservable_.EmitMessage(observer, message);
+      try
+      {
+        boost::unique_lock<boost::shared_mutex>  lock(mutex_);
+        oracleObservable_.EmitMessage(observer, message);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while emitting a message: " << e.What();
+      }
     }
 
 
@@ -802,12 +1333,15 @@
 
     OrthancStone::CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
     {
-      if (frame >= imageInformation_.GetNumberOfFrames())
+      if (frame == 0)
+      {
+        return geometry_;
+      }
+      else if (frame >= imageInformation_.GetNumberOfFrames())
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
-
-      if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
+      else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
       {
         if (frame >= frameOffsets_.size())
         {
@@ -819,6 +1353,10 @@
           geometry_.GetAxisX(),
           geometry_.GetAxisY());
       }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
     }
 
     bool FrameContainsPlane(unsigned int frame,
@@ -921,10 +1459,10 @@
     class MessageHandler : public Orthanc::IDynamicObject
     {
     public:
-      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const = 0;
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const = 0;
     };
 
-    void Handle(const OrthancApiOracleCommand::SuccessMessage& message)
+    void Handle(const OrthancRestApiCommand::SuccessMessage& message)
     {
       dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message);
     }
@@ -941,7 +1479,7 @@
       {
       }
 
-      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
       {
         Json::Value value;
         message.ParseJsonBody(value);
@@ -975,7 +1513,7 @@
       {
       }
 
-      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
+      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const
       {
         Json::Value value;
         message.ParseJsonBody(value);
@@ -1003,7 +1541,7 @@
       active_(false)
     {
       oracle.RegisterObserverCallback(
-        new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage>
+        new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancRestApiCommand::SuccessMessage>
         (*this, &AxialVolumeOrthancLoader::Handle));
     }
 
@@ -1017,7 +1555,7 @@
 
       active_ = true;
 
-      std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
+      std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
       command->SetUri("/series/" + seriesId + "/instances-tags");
       command->SetPayload(new LoadSeriesGeometryHandler(*this));
 
@@ -1039,7 +1577,7 @@
 
       // TODO => Should be part of a second call if needed
 
-      std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
+      std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand);
       command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
       command->SetPayload(new LoadInstanceGeometryHandler(*this));
 
@@ -1054,7 +1592,7 @@
 class Toto : public OrthancStone::IObserver
 {
 private:
-  void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message)
+  void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message)
   {
     Json::Value v;
     message.ParseJsonBody(v);
@@ -1062,9 +1600,30 @@
     printf("ICI [%s]\n", v.toStyledString().c_str());
   }
 
-  void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message)
+  void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message)
+  {
+    printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
+  }
+
+  void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
+  {
+    printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
+  }
+
+  void Handle(const Refactoring::OracleCommandExceptionMessage& message)
   {
-    printf("ERROR %d\n", message.GetHttpStatus());
+    printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType());
+
+    switch (message.GetCommand().GetType())
+    {
+      case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg:
+        printf("URI: [%s]\n", dynamic_cast<const Refactoring::GetOrthancWebViewerJpegCommand&>
+               (message.GetCommand()).GetUri().c_str());
+        break;
+      
+      default:
+        break;
+    }
   }
 
 public:
@@ -1073,7 +1632,19 @@
   {
     oracle.RegisterObserverCallback
       (new OrthancStone::Callable
-       <Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle));
+       <Toto, Refactoring::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracle.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, Refactoring::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracle.RegisterObserverCallback
+      (new OrthancStone::Callable
+      <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
+
+    oracle.RegisterObserverCallback
+      (new OrthancStone::Callable
+       <Toto, Refactoring::OracleCommandExceptionMessage>(*this, &Toto::Handle));
   }
 };
 
@@ -1101,12 +1672,13 @@
 
   oracle.Start();
 
+  if (1)
   {
     Json::Value v = Json::objectValue;
     v["Level"] = "Series";
     v["Query"] = Json::objectValue;
 
-    std::auto_ptr<Refactoring::OrthancApiOracleCommand>  command(new Refactoring::OrthancApiOracleCommand);
+    std::auto_ptr<Refactoring::OrthancRestApiCommand>  command(new Refactoring::OrthancRestApiCommand);
     command->SetMethod(Orthanc::HttpMethod_Post);
     command->SetUri("/tools/find");
     command->SetBody(v);
@@ -1114,11 +1686,64 @@
     oracle.Schedule(*toto, command.release());
   }
   
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept-Encoding", "gzip");
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+    oracle.Schedule(*toto, command.release());
+  }
+  
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancImageCommand>  command(new Refactoring::GetOrthancImageCommand);
+    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
+    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
+    oracle.Schedule(*toto, command.release());
+  }
+
+  if (1)
+  {
+    std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand>  command(new Refactoring::GetOrthancWebViewerJpegCommand);
+    command->SetHttpHeader("Accept-Encoding", "gzip");
+    command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
+    command->SetQuality(90);
+    oracle.Schedule(*toto, command.release());
+  }
+
+
   // 2017-11-17-Anonymized
   loader1->LoadSeries(oracle, "cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
   loader2->LoadInstance(oracle, "41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
 
-  boost::this_thread::sleep(boost::posix_time::seconds(1));
+  LOG(WARNING) << "...Waiting for Ctrl-C...";
+  Orthanc::SystemToolbox::ServerBarrier();
+  //boost::this_thread::sleep(boost::posix_time::seconds(1));
 
   oracle.Stop();
 }