changeset 746:d716bfb3e07c

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 May 2019 12:48:57 +0200
parents c44c1d2d3598
children ab236bb5dbc7
files Framework/Oracle/GetOrthancImageCommand.cpp Framework/Oracle/GetOrthancImageCommand.h Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Framework/Oracle/GetOrthancWebViewerJpegCommand.h Framework/Oracle/OrthancRestApiCommand.cpp Framework/Oracle/OrthancRestApiCommand.h Framework/Toolbox/DicomInstanceParameters.cpp Framework/Toolbox/DicomInstanceParameters.h Resources/CMake/OrthancStoneConfiguration.cmake Samples/Sdl/Loader.cpp
diffstat 10 files changed, 1424 insertions(+), 1090 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancImageCommand.cpp	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,153 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "GetOrthancImageCommand.h"
+
+#include <Core/Images/JpegReader.h>
+#include <Core/Images/PamReader.h>
+#include <Core/Images/PngReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+namespace OrthancStone
+{
+  GetOrthancImageCommand::SuccessMessage::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);
+    }
+  }
+
+
+  GetOrthancImageCommand::GetOrthancImageCommand() :
+    uri_("/"),
+    timeout_(10),
+    hasExpectedFormat_(false)
+  {
+  }
+
+
+  void GetOrthancImageCommand::SetExpectedPixelFormat(Orthanc::PixelFormat format)
+  {
+    hasExpectedFormat_ = true;
+    expectedFormat_ = format;
+  }
+
+
+  void GetOrthancImageCommand::SetInstanceUri(const std::string& instance,
+                                              Orthanc::PixelFormat pixelFormat)
+  {
+    uri_ = "/instances/" + instance;
+          
+    switch (pixelFormat)
+    {
+      case Orthanc::PixelFormat_RGB24:
+        uri_ += "/preview";
+        break;
+      
+      case Orthanc::PixelFormat_Grayscale16:
+        uri_ += "/image-uint16";
+        break;
+      
+      case Orthanc::PixelFormat_SignedGrayscale16:
+        uri_ += "/image-int16";
+        break;
+      
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  void GetOrthancImageCommand::ProcessHttpAnswer(IMessageEmitter& emitter,
+                                                 const 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)));
+    }
+
+    if (hasExpectedFormat_)
+    {
+      if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 &&
+          image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+      }
+
+      if (expectedFormat_ != image->GetFormat())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+      }
+    }
+
+    SuccessMessage message(*this, image.release(), contentType);
+    emitter.EmitMessage(receiver, message);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancImageCommand.h	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,119 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../Messages/IMessageEmitter.h"
+#include "OracleCommandWithPayload.h"
+
+#include <Core/Images/ImageAccessor.h>
+
+#include <map>
+
+namespace OrthancStone
+{
+  class GetOrthancImageCommand : public OracleCommandWithPayload
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class SuccessMessage : public 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);
+
+      const Orthanc::ImageAccessor& GetImage() const
+      {
+        return *image_;
+      }
+
+      Orthanc::MimeType GetMimeType() const
+      {
+        return mime_;
+      }
+    };
+
+
+  private:
+    std::string           uri_;
+    HttpHeaders           headers_;
+    unsigned int          timeout_;
+    bool                  hasExpectedFormat_;
+    Orthanc::PixelFormat  expectedFormat_;
+
+  public:
+    GetOrthancImageCommand();
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancImage;
+    }
+
+    void SetExpectedPixelFormat(Orthanc::PixelFormat format);
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = uri;
+    }
+
+    void SetInstanceUri(const std::string& instance,
+                        Orthanc::PixelFormat pixelFormat);
+
+    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 IObserver& receiver,
+                           const std::string& answer,
+                           const HttpHeaders& answerHeaders) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,217 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "GetOrthancWebViewerJpegCommand.h"
+
+#include "../Toolbox/LinearAlgebra.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/JpegReader.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <json/reader.h>
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  GetOrthancWebViewerJpegCommand::SuccessMessage::SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
+                                                                 Orthanc::ImageAccessor* image) :   // Takes ownership
+    OriginMessage(command),
+    image_(image)
+  {
+    if (image == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  GetOrthancWebViewerJpegCommand::GetOrthancWebViewerJpegCommand() :
+    frame_(0),
+    quality_(95),
+    timeout_(10),
+    expectedFormat_(Orthanc::PixelFormat_Grayscale8)
+  {
+  }
+
+
+  void GetOrthancWebViewerJpegCommand::SetQuality(unsigned int quality)
+  {
+    if (quality <= 0 ||
+        quality > 100)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      quality_ = quality;
+    }
+  }
+
+
+  std::string GetOrthancWebViewerJpegCommand::GetUri() const
+  {
+    return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
+            "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
+  }
+
+
+  void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IMessageEmitter& emitter,
+                                                         const 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 (!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);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.h	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,135 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../Messages/IMessageEmitter.h"
+#include "OracleCommandWithPayload.h"
+
+#include <Core/Images/ImageAccessor.h>
+
+#include <map>
+
+namespace OrthancStone
+{
+  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class SuccessMessage : public OriginMessage<GetOrthancWebViewerJpegCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      std::auto_ptr<Orthanc::ImageAccessor>  image_;
+
+    public:
+      SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
+                     Orthanc::ImageAccessor* image);   // Takes ownership
+
+      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_;
+
+  public:
+    GetOrthancWebViewerJpegCommand();
+
+    virtual Type GetType() const
+    {
+      return Type_GetOrthancWebViewerJpeg;
+    }
+
+    void SetExpectedPixelFormat(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);
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    Orthanc::PixelFormat GetExpectedPixelFormat() 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;
+
+    void ProcessHttpAnswer(IMessageEmitter& emitter,
+                           const IObserver& receiver,
+                           const std::string& answer) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OrthancRestApiCommand.cpp	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,78 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "OrthancRestApiCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <json/reader.h>
+#include <json/writer.h>
+
+namespace OrthancStone
+{
+  OrthancRestApiCommand::SuccessMessage::SuccessMessage(const OrthancRestApiCommand& command,
+                                                        const HttpHeaders& answerHeaders,
+                                                        std::string& answer) :
+    OriginMessage(command),
+    headers_(answerHeaders),
+    answer_(answer)
+  {
+  }
+
+
+  void OrthancRestApiCommand::SuccessMessage::ParseJsonBody(Json::Value& target) const
+  {
+    Json::Reader reader;
+    if (!reader.parse(answer_, target))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  OrthancRestApiCommand::OrthancRestApiCommand() :
+    method_(Orthanc::HttpMethod_Get),
+    uri_("/"),
+    timeout_(10)
+  {
+  }
+
+
+  void OrthancRestApiCommand::SetBody(const Json::Value& json)
+  {
+    Json::FastWriter writer;
+    body_ = writer.write(json);
+  }
+
+
+  const std::string& OrthancRestApiCommand::GetBody() const
+  {
+    if (method_ == Orthanc::HttpMethod_Post ||
+        method_ == Orthanc::HttpMethod_Put)
+    {
+      return body_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/OrthancRestApiCommand.h	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,131 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../Messages/IMessage.h"
+#include "OracleCommandWithPayload.h"
+
+#include <Core/Enumerations.h>
+
+#include <map>
+#include <json/value.h>
+
+namespace OrthancStone
+{
+  class OrthancRestApiCommand : public OracleCommandWithPayload
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class SuccessMessage : public OriginMessage<OrthancRestApiCommand>
+    {
+      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+      
+    private:
+      HttpHeaders   headers_;
+      std::string   answer_;
+
+    public:
+      SuccessMessage(const OrthancRestApiCommand& command,
+                     const HttpHeaders& answerHeaders,
+                     std::string& answer  /* will be swapped to avoid a memcpy() */);
+
+      const std::string& GetAnswer() const
+      {
+        return answer_;
+      }
+
+      void ParseJsonBody(Json::Value& target) const;
+
+      const HttpHeaders&  GetAnswerHeaders() const
+      {
+        return headers_;
+      }
+    };
+
+
+  private:
+    Orthanc::HttpMethod  method_;
+    std::string          uri_;
+    std::string          body_;
+    HttpHeaders          headers_;
+    unsigned int         timeout_;
+
+  public:
+    OrthancRestApiCommand();
+
+    virtual Type GetType() const
+    {
+      return Type_OrthancRestApi;
+    }
+
+    void SetMethod(Orthanc::HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = uri;
+    }
+
+    void SetBody(const std::string& body)
+    {
+      body_ = body;
+    }
+
+    void SetBody(const Json::Value& json);
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    Orthanc::HttpMethod GetMethod() const
+    {
+      return method_;
+    }
+
+    const std::string& GetUri() const
+    {
+      return uri_;
+    }
+
+    const std::string& GetBody() const;
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomInstanceParameters.cpp	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,378 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "DicomInstanceParameters.h"
+
+#include "../Scene2D/ColorTextureSceneLayer.h"
+#include "../Scene2D/FloatTextureSceneLayer.h"
+#include "../Toolbox/GeometryToolbox.h"
+
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+
+namespace OrthancStone
+{
+  void DicomInstanceParameters::Data::ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
+  {
+    // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
+
+    {
+      std::string increment;
+
+      if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
+      {
+        Orthanc::Toolbox::ToUpperCase(increment);
+        if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
+        {
+          LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
+          return;
+        }
+      }
+    }
+
+    if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
+        frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
+    {
+      LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
+      frameOffsets_.clear();
+    }
+    else
+    {
+      if (frameOffsets_.size() >= 2)
+      {
+        thickness_ = frameOffsets_[1] - frameOffsets_[0];
+
+        if (thickness_ < 0)
+        {
+          thickness_ = -thickness_;
+        }
+      }
+    }
+  }
+
+
+  DicomInstanceParameters::Data::Data(const Orthanc::DicomMap& dicom) :
+    imageInformation_(dicom)
+  {
+    if (imageInformation_.GetNumberOfFrames() <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
+        !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
+        !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+        
+    std::string s;
+    if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      sopClassUid_ = StringToSopClassUid(s);
+    }
+
+    if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
+    {
+      thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
+    }
+
+    GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
+
+    std::string position, orientation;
+    if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
+        dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
+    {
+      geometry_ = CoordinateSystem3D(position, orientation);
+    }
+
+    if (sopClassUid_ == SopClassUid_RTDose)
+    {
+      ComputeDoseOffsets(dicom);
+    }
+
+    isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
+                imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
+
+    double doseGridScaling;
+
+    if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
+        dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
+    {
+      hasRescale_ = true;
+    }
+    else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
+    {
+      hasRescale_ = true;
+      rescaleIntercept_ = 0;
+      rescaleSlope_ = doseGridScaling;
+    }
+    else
+    {
+      hasRescale_ = false;
+    }
+
+    Vector c, w;
+    if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
+        LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
+        c.size() > 0 && 
+        w.size() > 0)
+    {
+      hasDefaultWindowing_ = true;
+      defaultWindowingCenter_ = static_cast<float>(c[0]);
+      defaultWindowingWidth_ = static_cast<float>(w[0]);
+    }
+    else
+    {
+      hasDefaultWindowing_ = false;
+    }
+
+    if (sopClassUid_ == SopClassUid_RTDose)
+    {
+      switch (imageInformation_.GetBitsStored())
+      {
+        case 16:
+          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
+          break;
+
+        case 32:
+          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      } 
+    }
+    else if (isColor_)
+    {
+      expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
+    }
+    else if (imageInformation_.IsSigned())
+    {
+      expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
+    }
+    else
+    {
+      expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
+    }
+  }
+
+
+  CoordinateSystem3D  DicomInstanceParameters::Data::GetFrameGeometry(unsigned int frame) const
+  {
+    if (frame == 0)
+    {
+      return geometry_;
+    }
+    else if (frame >= imageInformation_.GetNumberOfFrames())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else if (sopClassUid_ == SopClassUid_RTDose)
+    {
+      if (frame >= frameOffsets_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      return CoordinateSystem3D(
+        geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
+        geometry_.GetAxisX(),
+        geometry_.GetAxisY());
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+
+  bool DicomInstanceParameters::Data::IsPlaneWithinSlice(unsigned int frame,
+                                                         const CoordinateSystem3D& plane) const
+  {
+    if (frame >= imageInformation_.GetNumberOfFrames())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    CoordinateSystem3D tmp = geometry_;
+
+    if (frame != 0)
+    {
+      tmp = GetFrameGeometry(frame);
+    }
+
+    double distance;
+
+    return (CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
+            distance <= thickness_ / 2.0);
+  }
+
+      
+  void DicomInstanceParameters::Data::ApplyRescale(Orthanc::ImageAccessor& image,
+                                                   bool useDouble) const
+  {
+    if (image.GetFormat() != Orthanc::PixelFormat_Float32)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+    
+    if (hasRescale_)
+    {
+      const unsigned int width = image.GetWidth();
+      const unsigned int height = image.GetHeight();
+        
+      for (unsigned int y = 0; y < height; y++)
+      {
+        float* p = reinterpret_cast<float*>(image.GetRow(y));
+
+        if (useDouble)
+        {
+          // Slower, accurate implementation using double
+          for (unsigned int x = 0; x < width; x++, p++)
+          {
+            double value = static_cast<double>(*p);
+            *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
+          }
+        }
+        else
+        {
+          // Fast, approximate implementation using float
+          for (unsigned int x = 0; x < width; x++, p++)
+          {
+            *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
+          }
+        }
+      }
+    }
+  }
+
+  double DicomInstanceParameters::GetRescaleIntercept() const
+  {
+    if (data_.hasRescale_)
+    {
+      return data_.rescaleIntercept_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  double DicomInstanceParameters::GetRescaleSlope() const
+  {
+    if (data_.hasRescale_)
+    {
+      return data_.rescaleSlope_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  float DicomInstanceParameters::GetDefaultWindowingCenter() const
+  {
+    if (data_.hasDefaultWindowing_)
+    {
+      return data_.defaultWindowingCenter_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  float DicomInstanceParameters::GetDefaultWindowingWidth() const
+  {
+    if (data_.hasDefaultWindowing_)
+    {
+      return data_.defaultWindowingWidth_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture(const Orthanc::ImageAccessor& pixelData) const
+  {
+    assert(sizeof(float) == 4);
+
+    Orthanc::PixelFormat sourceFormat = pixelData.GetFormat();
+
+    if (sourceFormat != GetExpectedPixelFormat())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
+    }
+
+    if (sourceFormat == Orthanc::PixelFormat_RGB24)
+    {
+      // This is the case of a color image. No conversion has to be done.
+      return new ColorTextureSceneLayer(pixelData);
+    }
+    else
+    {
+      if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
+          sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
+          sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      std::auto_ptr<FloatTextureSceneLayer> texture;
+        
+      {
+        // This is the case of a grayscale frame. Convert it to Float32.
+        std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
+                                                                   pixelData.GetWidth(), 
+                                                                   pixelData.GetHeight(),
+                                                                   false));
+        Orthanc::ImageProcessing::Convert(*converted, pixelData);
+
+        // Correct rescale slope/intercept if need be
+        data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
+
+        texture.reset(new FloatTextureSceneLayer(*converted));
+      }
+
+      if (data_.hasDefaultWindowing_)
+      {
+        texture->SetCustomWindowing(data_.defaultWindowingCenter_,
+                                    data_.defaultWindowingWidth_);
+      }
+        
+      return texture.release();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/DicomInstanceParameters.h	Wed May 22 12:48:57 2019 +0200
@@ -0,0 +1,185 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../StoneEnumerations.h"
+#include "../Scene2D/TextureBaseSceneLayer.h"
+#include "../Toolbox/CoordinateSystem3D.h"
+
+#include <Core/IDynamicObject.h>
+#include <Core/DicomFormat/DicomImageInformation.h>
+
+namespace OrthancStone
+{
+  class DicomInstanceParameters :
+    public Orthanc::IDynamicObject  /* to be used as a payload to SlicesSorter */
+  {
+    // This class supersedes the deprecated "DicomFrameConverter"
+
+  private:
+    struct Data   // Struct to ease the copy constructor
+    {
+      std::string                       orthancInstanceId_;
+      std::string                       studyInstanceUid_;
+      std::string                       seriesInstanceUid_;
+      std::string                       sopInstanceUid_;
+      Orthanc::DicomImageInformation    imageInformation_;
+      SopClassUid                       sopClassUid_;
+      double                            thickness_;
+      double                            pixelSpacingX_;
+      double                            pixelSpacingY_;
+      CoordinateSystem3D                geometry_;
+      Vector                            frameOffsets_;
+      bool                              isColor_;
+      bool                              hasRescale_;
+      double                            rescaleIntercept_;
+      double                            rescaleSlope_;
+      bool                              hasDefaultWindowing_;
+      float                             defaultWindowingCenter_;
+      float                             defaultWindowingWidth_;
+      Orthanc::PixelFormat              expectedPixelFormat_;
+
+      void ComputeDoseOffsets(const Orthanc::DicomMap& dicom);
+
+      Data(const Orthanc::DicomMap& dicom);
+
+      CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const;
+
+      bool IsPlaneWithinSlice(unsigned int frame,
+                              const CoordinateSystem3D& plane) const;
+      
+      void ApplyRescale(Orthanc::ImageAccessor& image,
+                        bool useDouble) const;
+    };
+
+    
+    Data  data_;
+
+
+  public:
+    DicomInstanceParameters(const DicomInstanceParameters& other) :
+    data_(other.data_)
+    {
+    }
+
+    DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
+      data_(dicom)
+    {
+    }
+
+    void SetOrthancInstanceIdentifier(const std::string& id)
+    {
+      data_.orthancInstanceId_ = id;
+    }
+
+    const std::string& GetOrthancInstanceIdentifier() const
+    {
+      return data_.orthancInstanceId_;
+    }
+
+    const Orthanc::DicomImageInformation& GetImageInformation() const
+    {
+      return data_.imageInformation_;
+    }
+
+    const std::string& GetStudyInstanceUid() const
+    {
+      return data_.studyInstanceUid_;
+    }
+
+    const std::string& GetSeriesInstanceUid() const
+    {
+      return data_.seriesInstanceUid_;
+    }
+
+    const std::string& GetSopInstanceUid() const
+    {
+      return data_.sopInstanceUid_;
+    }
+
+    SopClassUid GetSopClassUid() const
+    {
+      return data_.sopClassUid_;
+    }
+
+    double GetThickness() const
+    {
+      return data_.thickness_;
+    }
+
+    double GetPixelSpacingX() const
+    {
+      return data_.pixelSpacingX_;
+    }
+
+    double GetPixelSpacingY() const
+    {
+      return data_.pixelSpacingY_;
+    }
+
+    const CoordinateSystem3D&  GetGeometry() const
+    {
+      return data_.geometry_;
+    }
+
+    CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
+    {
+      return data_.GetFrameGeometry(frame);
+    }
+
+    bool IsPlaneWithinSlice(unsigned int frame,
+                            const CoordinateSystem3D& plane) const
+    {
+      return data_.IsPlaneWithinSlice(frame, plane);
+    }
+
+    bool IsColor() const
+    {
+      return data_.isColor_;
+    }
+
+    bool HasRescale() const
+    {
+      return data_.hasRescale_;
+    }
+
+    double GetRescaleIntercept() const;
+
+    double GetRescaleSlope() const;
+
+    bool HasDefaultWindowing() const
+    {
+      return data_.hasDefaultWindowing_;
+    }
+
+    float GetDefaultWindowingCenter() const;
+
+    float GetDefaultWindowingWidth() const;
+
+    Orthanc::PixelFormat GetExpectedPixelFormat() const
+    {
+      return data_.expectedPixelFormat_;
+    }
+
+    TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& pixelData) const;
+  };
+}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 22 12:08:15 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 22 12:48:57 2019 +0200
@@ -32,7 +32,6 @@
 
 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
 include_directories(${ORTHANC_ROOT})
-include_directories(${ORTHANC_ROOT}/Core/Images) # hack for the numerous #include "../Enumerations.h" in Orthanc to work
 
 
 #####################################################################
@@ -430,7 +429,10 @@
   ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancImageCommand.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Oracle/OracleCommandWithPayload.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Oracle/OrthancRestApiCommand.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyAlphaLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp
@@ -452,6 +454,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/StoneInitialization.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/AffineTransform2D.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/CoordinateSystem3D.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomInstanceParameters.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DynamicBitmap.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.cpp
--- a/Samples/Sdl/Loader.cpp	Wed May 22 12:08:15 2019 +0200
+++ b/Samples/Sdl/Loader.cpp	Wed May 22 12:48:57 2019 +0200
@@ -19,6 +19,10 @@
  **/
 
 
+#include "../../Framework/Toolbox/DicomInstanceParameters.h"
+#include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h"
+#include "../../Framework/Oracle/GetOrthancImageCommand.h"
+#include "../../Framework/Oracle/OrthancRestApiCommand.h"
 #include "../../Framework/Oracle/SleepOracleCommand.h"
 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h"
 #include "../../Framework/Messages/IMessageEmitter.h"
@@ -28,12 +32,6 @@
 // From Stone
 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
 #include "../../Framework/Loaders/BasicFetchingStrategy.h"
-#include "../../Framework/Messages/ICallable.h"
-#include "../../Framework/Messages/IMessage.h"
-#include "../../Framework/Messages/IObservable.h"
-#include "../../Framework/Messages/MessageBroker.h"
-#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
-#include "../../Framework/Scene2D/FloatTextureSceneLayer.h"
 #include "../../Framework/Scene2D/Scene2D.h"
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/Toolbox/GeometryToolbox.h"
@@ -48,12 +46,7 @@
 #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>
@@ -71,1075 +64,15 @@
 
 namespace OrthancStone
 {
-  typedef std::map<std::string, std::string>  HttpHeaders;
-
-  class OrthancRestApiCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public OriginMessage<OrthancRestApiCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-      
-    private:
-      HttpHeaders   headers_;
-      std::string   answer_;
-
-    public:
-      SuccessMessage(const OrthancRestApiCommand& command,
-                     const HttpHeaders& answerHeaders,
-                     std::string& answer  /* will be swapped to avoid a memcpy() */) :
-        OriginMessage(command),
-        headers_(answerHeaders),
-        answer_(answer)
-      {
-      }
-
-      const std::string& GetAnswer() const
-      {
-        return answer_;
-      }
-
-      void ParseJsonBody(Json::Value& target) const
-      {
-        Json::Reader reader;
-        if (!reader.parse(answer_, target))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-      }
-
-      const HttpHeaders&  GetAnswerHeaders() const
-      {
-        return headers_;
-      }
-    };
-
-
-  private:
-    Orthanc::HttpMethod  method_;
-    std::string          uri_;
-    std::string          body_;
-    HttpHeaders          headers_;
-    unsigned int         timeout_;
-
-  public:
-    OrthancRestApiCommand() :
-      method_(Orthanc::HttpMethod_Get),
-      uri_("/"),
-      timeout_(10)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_OrthancRestApi;
-    }
-
-    void SetMethod(Orthanc::HttpMethod method)
-    {
-      method_ = method;
-    }
-
-    void SetUri(const std::string& uri)
-    {
-      uri_ = uri;
-    }
-
-    void SetBody(const std::string& body)
-    {
-      body_ = body;
-    }
-
-    void SetBody(const Json::Value& json)
-    {
-      Json::FastWriter writer;
-      body_ = writer.write(json);
-    }
-
-    void SetHttpHeader(const std::string& key,
-                       const std::string& value)
-    {
-      headers_[key] = value;
-    }
-
-    Orthanc::HttpMethod GetMethod() const
-    {
-      return method_;
-    }
-
-    const std::string& GetUri() const
-    {
-      return uri_;
-    }
-
-    const std::string& GetBody() const
-    {
-      if (method_ == Orthanc::HttpMethod_Post ||
-          method_ == Orthanc::HttpMethod_Put)
-      {
-        return body_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    const HttpHeaders& GetHttpHeaders() const
-    {
-      return headers_;
-    }
-
-    void SetTimeout(unsigned int seconds)
-    {
-      timeout_ = seconds;
-    }
-
-    unsigned int GetTimeout() const
-    {
-      return timeout_;
-    }
-  };
-
-
-
-
-  class GetOrthancImageCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public 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_;
-    bool                  hasExpectedFormat_;
-    Orthanc::PixelFormat  expectedFormat_;
-
-  public:
-    GetOrthancImageCommand() :
-      uri_("/"),
-      timeout_(10),
-      hasExpectedFormat_(false)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_GetOrthancImage;
-    }
-
-    void SetExpectedPixelFormat(Orthanc::PixelFormat format)
-    {
-      hasExpectedFormat_ = true;
-      expectedFormat_ = format;
-    }
-
-    void SetUri(const std::string& uri)
-    {
-      uri_ = uri;
-    }
-
-    void SetInstanceUri(const std::string& instance,
-                        Orthanc::PixelFormat pixelFormat)
-    {
-      uri_ = "/instances/" + instance;
-          
-      switch (pixelFormat)
-      {
-        case Orthanc::PixelFormat_RGB24:
-          uri_ += "/preview";
-          break;
-      
-        case Orthanc::PixelFormat_Grayscale16:
-          uri_ += "/image-uint16";
-          break;
-      
-        case Orthanc::PixelFormat_SignedGrayscale16:
-          uri_ += "/image-int16";
-          break;
-      
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-    }
-
-    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 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)));
-      }
-
-      if (hasExpectedFormat_)
-      {
-        if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 &&
-            image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
-        {
-          image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
-        }
-
-        if (expectedFormat_ != image->GetFormat())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-        }
-      }
-
-      SuccessMessage message(*this, image.release(), contentType);
-      emitter.EmitMessage(receiver, message);
-    }
-  };
-
-
-
-  class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
-  {
-  public:
-    class SuccessMessage : public 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_;
-
-  public:
-    GetOrthancWebViewerJpegCommand() :
-      frame_(0),
-      quality_(95),
-      timeout_(10),
-      expectedFormat_(Orthanc::PixelFormat_Grayscale8)
-    {
-    }
-
-    virtual Type GetType() const
-    {
-      return Type_GetOrthancWebViewerJpeg;
-    }
-
-    void SetExpectedPixelFormat(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 GetExpectedPixelFormat() 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 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 (!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 DicomInstanceParameters :
-    public Orthanc::IDynamicObject  /* to be used as a payload of SlicesSorter */
-  {
-  private:
-    struct Data   // Struct to ease the copy constructor
-    {
-      std::string                       orthancInstanceId_;
-      std::string                       studyInstanceUid_;
-      std::string                       seriesInstanceUid_;
-      std::string                       sopInstanceUid_;
-      Orthanc::DicomImageInformation    imageInformation_;
-      SopClassUid         sopClassUid_;
-      double                            thickness_;
-      double                            pixelSpacingX_;
-      double                            pixelSpacingY_;
-      CoordinateSystem3D  geometry_;
-      Vector              frameOffsets_;
-      bool                              isColor_;
-      bool                              hasRescale_;
-      double                            rescaleIntercept_;
-      double                            rescaleSlope_;
-      bool                              hasDefaultWindowing_;
-      float                             defaultWindowingCenter_;
-      float                             defaultWindowingWidth_;
-      Orthanc::PixelFormat              expectedPixelFormat_;
-
-      void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
-      {
-        // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
-
-        {
-          std::string increment;
-
-          if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
-          {
-            Orthanc::Toolbox::ToUpperCase(increment);
-            if (increment != "3004,000C")  // This is the "Grid Frame Offset Vector" tag
-            {
-              LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
-              return;
-            }
-          }
-        }
-
-        if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
-            frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
-        {
-          LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
-          frameOffsets_.clear();
-        }
-        else
-        {
-          if (frameOffsets_.size() >= 2)
-          {
-            thickness_ = frameOffsets_[1] - frameOffsets_[0];
-
-            if (thickness_ < 0)
-            {
-              thickness_ = -thickness_;
-            }
-          }
-        }
-      }
-
-      Data(const Orthanc::DicomMap& dicom) :
-        imageInformation_(dicom)
-      {
-        if (imageInformation_.GetNumberOfFrames() <= 0)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
-            !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
-            !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        
-        std::string s;
-        if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-        }
-        else
-        {
-          sopClassUid_ = StringToSopClassUid(s);
-        }
-
-        if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
-        {
-          thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
-        }
-
-        GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
-
-        std::string position, orientation;
-        if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
-            dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
-        {
-          geometry_ = CoordinateSystem3D(position, orientation);
-        }
-
-        if (sopClassUid_ == SopClassUid_RTDose)
-        {
-          ComputeDoseOffsets(dicom);
-        }
-
-        isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
-                    imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
-
-        double doseGridScaling;
-
-        if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
-            dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
-        {
-          hasRescale_ = true;
-        }
-        else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
-        {
-          hasRescale_ = true;
-          rescaleIntercept_ = 0;
-          rescaleSlope_ = doseGridScaling;
-        }
-        else
-        {
-          hasRescale_ = false;
-        }
-
-        Vector c, w;
-        if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
-            LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
-            c.size() > 0 && 
-            w.size() > 0)
-        {
-          hasDefaultWindowing_ = true;
-          defaultWindowingCenter_ = static_cast<float>(c[0]);
-          defaultWindowingWidth_ = static_cast<float>(w[0]);
-        }
-        else
-        {
-          hasDefaultWindowing_ = false;
-        }
-
-        if (sopClassUid_ == SopClassUid_RTDose)
-        {
-          switch (imageInformation_.GetBitsStored())
-          {
-            case 16:
-              expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-              break;
-
-            case 32:
-              expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-          } 
-        }
-        else if (isColor_)
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
-        }
-        else if (imageInformation_.IsSigned())
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
-        }
-        else
-        {
-          expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
-        }
-      }
-
-      CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
-      {
-        if (frame == 0)
-        {
-          return geometry_;
-        }
-        else if (frame >= imageInformation_.GetNumberOfFrames())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-        else if (sopClassUid_ == SopClassUid_RTDose)
-        {
-          if (frame >= frameOffsets_.size())
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-
-          return CoordinateSystem3D(
-            geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
-            geometry_.GetAxisX(),
-            geometry_.GetAxisY());
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-      }
-
-      // TODO - Is this necessary?
-      bool FrameContainsPlane(unsigned int frame,
-                              const CoordinateSystem3D& plane) const
-      {
-        if (frame >= imageInformation_.GetNumberOfFrames())
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-        }
-
-        CoordinateSystem3D tmp = geometry_;
-
-        if (frame != 0)
-        {
-          tmp = GetFrameGeometry(frame);
-        }
-
-        double distance;
-
-        return (CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
-                distance <= thickness_ / 2.0);
-      }
-
-      
-      void ApplyRescale(Orthanc::ImageAccessor& image,
-                        bool useDouble) const
-      {
-        if (image.GetFormat() != Orthanc::PixelFormat_Float32)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-        }
-    
-        if (hasRescale_)
-        {
-          const unsigned int width = image.GetWidth();
-          const unsigned int height = image.GetHeight();
-        
-          for (unsigned int y = 0; y < height; y++)
-          {
-            float* p = reinterpret_cast<float*>(image.GetRow(y));
-
-            if (useDouble)
-            {
-              // Slower, accurate implementation using double
-              for (unsigned int x = 0; x < width; x++, p++)
-              {
-                double value = static_cast<double>(*p);
-                *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
-              }
-            }
-            else
-            {
-              // Fast, approximate implementation using float
-              for (unsigned int x = 0; x < width; x++, p++)
-              {
-                *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
-              }
-            }
-          }
-        }
-      }
-    };
-
-    
-    Data  data_;
-
-
-  public:
-    DicomInstanceParameters(const DicomInstanceParameters& other) :
-    data_(other.data_)
-    {
-    }
-
-    DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
-      data_(dicom)
-    {
-    }
-
-    void SetOrthancInstanceIdentifier(const std::string& id)
-    {
-      data_.orthancInstanceId_ = id;
-    }
-
-    const std::string& GetOrthancInstanceIdentifier() const
-    {
-      return data_.orthancInstanceId_;
-    }
-
-    const Orthanc::DicomImageInformation& GetImageInformation() const
-    {
-      return data_.imageInformation_;
-    }
-
-    const std::string& GetStudyInstanceUid() const
-    {
-      return data_.studyInstanceUid_;
-    }
-
-    const std::string& GetSeriesInstanceUid() const
-    {
-      return data_.seriesInstanceUid_;
-    }
-
-    const std::string& GetSopInstanceUid() const
-    {
-      return data_.sopInstanceUid_;
-    }
-
-    SopClassUid GetSopClassUid() const
-    {
-      return data_.sopClassUid_;
-    }
-
-    double GetThickness() const
-    {
-      return data_.thickness_;
-    }
-
-    double GetPixelSpacingX() const
-    {
-      return data_.pixelSpacingX_;
-    }
-
-    double GetPixelSpacingY() const
-    {
-      return data_.pixelSpacingY_;
-    }
-
-    const CoordinateSystem3D&  GetGeometry() const
-    {
-      return data_.geometry_;
-    }
-
-    CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
-    {
-      return data_.GetFrameGeometry(frame);
-    }
-
-    // TODO - Is this necessary?
-    bool FrameContainsPlane(unsigned int frame,
-                            const CoordinateSystem3D& plane) const
-    {
-      return data_.FrameContainsPlane(frame, plane);
-    }
-
-    bool IsColor() const
-    {
-      return data_.isColor_;
-    }
-
-    bool HasRescale() const
-    {
-      return data_.hasRescale_;
-    }
-
-    double GetRescaleIntercept() const
-    {
-      if (data_.hasRescale_)
-      {
-        return data_.rescaleIntercept_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    double GetRescaleSlope() const
-    {
-      if (data_.hasRescale_)
-      {
-        return data_.rescaleSlope_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    bool HasDefaultWindowing() const
-    {
-      return data_.hasDefaultWindowing_;
-    }
-
-    float GetDefaultWindowingCenter() const
-    {
-      if (data_.hasDefaultWindowing_)
-      {
-        return data_.defaultWindowingCenter_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    float GetDefaultWindowingWidth() const
-    {
-      if (data_.hasDefaultWindowing_)
-      {
-        return data_.defaultWindowingWidth_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    Orthanc::PixelFormat GetExpectedPixelFormat() const
-    {
-      return data_.expectedPixelFormat_;
-    }
-
-
-    TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const
-    {
-      assert(sizeof(float) == 4);
-
-      Orthanc::PixelFormat sourceFormat = source.GetFormat();
-
-      if (sourceFormat != GetExpectedPixelFormat())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
-      }
-
-      if (sourceFormat == Orthanc::PixelFormat_RGB24)
-      {
-        // This is the case of a color image. No conversion has to be done.
-        return new ColorTextureSceneLayer(source);
-      }
-      else
-      {
-        if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
-            sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
-            sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-        }
-
-        std::auto_ptr<FloatTextureSceneLayer> texture;
-        
-        {
-          // This is the case of a grayscale frame. Convert it to Float32.
-          std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, 
-                                                                     source.GetWidth(), 
-                                                                     source.GetHeight(),
-                                                                     false));
-          Orthanc::ImageProcessing::Convert(*converted, source);
-
-          // Correct rescale slope/intercept if need be
-          data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
-
-          texture.reset(new FloatTextureSceneLayer(*converted));
-        }
-
-        if (data_.hasDefaultWindowing_)
-        {
-          texture->SetCustomWindowing(data_.defaultWindowingCenter_,
-                                      data_.defaultWindowingWidth_);
-        }
-        
-        return texture.release();
-      }
-    }
-  };
-
-
   class DicomVolumeImage : public boost::noncopyable
   {
   private:
-    std::auto_ptr<ImageBuffer3D>  image_;
-    std::auto_ptr<VolumeImageGeometry>  geometry_;
-    std::vector<DicomInstanceParameters*>       slices_;
-    uint64_t                                    revision_;
-    std::vector<uint64_t>                       slicesRevision_;
-    std::vector<unsigned int>                   slicesQuality_;
+    std::auto_ptr<ImageBuffer3D>           image_;
+    std::auto_ptr<VolumeImageGeometry>     geometry_;
+    std::vector<DicomInstanceParameters*>  slices_;
+    uint64_t                               revision_;
+    std::vector<uint64_t>                  slicesRevision_;
+    std::vector<unsigned int>              slicesQuality_;
 
     void CheckSlice(size_t index,
                     const DicomInstanceParameters& reference) const
@@ -1380,10 +313,10 @@
 
 
 
-  class IDicomVolumeSource : public boost::noncopyable
+  class IDicomVolumeImageSource : public boost::noncopyable
   {
   public:
-    virtual ~IDicomVolumeSource()
+    virtual ~IDicomVolumeImageSource()
     {
     }
 
@@ -1396,7 +329,7 @@
 
   class VolumeSeriesOrthancLoader :
     public IObserver,
-    public IDicomVolumeSource
+    public IDicomVolumeImageSource
   {
   private:
     static const unsigned int LOW_QUALITY = 0;
@@ -1687,19 +620,19 @@
   class DicomVolumeMPRSlicer : public IVolumeSlicer
   {
   private:
-    bool                            linearInterpolation_;
-    Scene2D&          scene_;
-    int                             layerDepth_;
-    IDicomVolumeSource&             source_;
-    bool                            first_;
-    VolumeProjection  lastProjection_;
-    unsigned int                    lastSliceIndex_;
-    uint64_t                        lastSliceRevision_;
+    bool                      linearInterpolation_;
+    Scene2D&                  scene_;
+    int                       layerDepth_;
+    IDicomVolumeImageSource&  source_;
+    bool                      first_;
+    VolumeProjection          lastProjection_;
+    unsigned int              lastSliceIndex_;
+    uint64_t                  lastSliceRevision_;
 
   public:
     DicomVolumeMPRSlicer(Scene2D& scene,
                          int layerDepth,
-                         IDicomVolumeSource& source) :
+                         IDicomVolumeImageSource& source) :
       linearInterpolation_(false),
       scene_(scene),
       layerDepth_(layerDepth),
@@ -1814,6 +747,8 @@
   class ThreadedOracle : public IOracle
   {
   private:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
     class Item : public Orthanc::IDynamicObject
     {
     private: