view Samples/Sdl/Loader.cpp @ 626:cc69acccd9f8

cont
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 May 2019 18:34:40 +0200
parents 2eeb5857eb43
children ea8322566596
line wrap: on
line source

/**
 * Stone of Orthanc
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-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/>.
 **/

// From Stone
#include "../../Framework/Messages/ICallable.h"
#include "../../Framework/Messages/IMessage.h"
#include "../../Framework/Messages/IObservable.h"
#include "../../Framework/Messages/MessageBroker.h"
#include "../../Framework/StoneInitialization.h"
#include "../../Framework/Toolbox/GeometryToolbox.h"
#include "../../Framework/Volumes/ImageBuffer3D.h"

// From Orthanc framework
#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/PngWriter.h>
#include <Core/Logging.h>
#include <Core/MultiThreading/SharedMessageQueue.h>
#include <Core/OrthancException.h>
#include <Core/Toolbox.h>

#include <json/reader.h>
#include <json/value.h>
#include <json/writer.h>

#include <list>
#include <stdio.h>



namespace Refactoring
{
  class IOracleCommand : public boost::noncopyable
  {
  public:
    enum Type
    {
      Type_OrthancApi
    };

    virtual ~IOracleCommand()
    {
    }

    virtual Type GetType() const = 0;
  };


  class IMessageEmitter : public boost::noncopyable
  {
  public:
    virtual ~IMessageEmitter()
    {
    }

    virtual void EmitMessage(const OrthancStone::IObserver& observer,
                             const OrthancStone::IMessage& message) = 0;
  };


  class IOracle : public boost::noncopyable
  {
  public:
    virtual ~IOracle()
    {
    }

    virtual void Schedule(const OrthancStone::IObserver& receiver,
                          IOracleCommand* command) = 0;  // Takes ownership
  };




  class OracleCommandWithPayload : public IOracleCommand
  {
  private:
    std::auto_ptr<Orthanc::IDynamicObject>  payload_;

  public:
    void SetPayload(Orthanc::IDynamicObject* payload)
    {
      if (payload == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
      else
      {
        payload_.reset(payload);
      }    
    }

    bool HasPayload() const
    {
      return (payload_.get() != NULL);
    }

    const Orthanc::IDynamicObject& GetPayload() const
    {
      if (HasPayload())
      {
        return *payload_;
      }
      else
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
    }
  };



  typedef std::map<std::string, std::string>  HttpHeaders;

  class OrthancApiOracleCommand : public OracleCommandWithPayload
  {
  public:
    class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess,   // TODO
                                                              OrthancApiOracleCommand>
    {
    private:
      HttpHeaders   headers_;
      std::string   answer_;

    public:
      SuccessMessage(const OrthancApiOracleCommand& 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_;
      }
    };


    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_;
    std::string          body_;
    HttpHeaders          headers_;
    unsigned int         timeout_;

    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
    std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> >  failureCallback_;

  public:
    OrthancApiOracleCommand() :
      method_(Orthanc::HttpMethod_Get),
      uri_("/"),
      timeout_(10)
    {
    }

    virtual Type GetType() const
    {
      return Type_OrthancApi;
    }

    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 NativeOracle : public IOracle
  {
  private:
    class Item : public Orthanc::IDynamicObject
    {
    private:
      const OrthancStone::IObserver&  receiver_;
      std::auto_ptr<IOracleCommand>   command_;

    public:
      Item(const OrthancStone::IObserver& receiver,
           IOracleCommand* command) :
        receiver_(receiver),
        command_(command)
      {
        if (command == NULL)
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
        }
      }

      const OrthancStone::IObserver& GetReceiver() const
      {
        return receiver_;
      }

      const IOracleCommand& GetCommand() const
      {
        assert(command_.get() != NULL);
        return *command_;
      }
    };


    enum State
    {
      State_Setup,
      State_Running,
      State_Stopped
    };


    IMessageEmitter&               emitter_;
    Orthanc::WebServiceParameters  orthanc_;
    Orthanc::SharedMessageQueue    queue_;
    State                          state_;
    boost::mutex                   mutex_;
    std::vector<boost::thread*>    workers_;


    void Execute(const OrthancStone::IObserver& receiver,
                 const OrthancApiOracleCommand& command)
    {
      Orthanc::HttpClient  client(orthanc_, command.GetUri());
      client.SetMethod(command.GetMethod());
      client.SetTimeout(command.GetTimeout());

      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;

      bool success;
      try
      {
        success = client.Apply(answer, answerHeaders);
      }
      catch (Orthanc::OrthancException& e)
      {
        success = false;
      }

      if (success)
      {
        OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer);
        emitter_.EmitMessage(receiver, message);
      }
      else
      {
        OrthancApiOracleCommand::FailureMessage message(command, client.GetLastStatus());
        emitter_.EmitMessage(receiver, message);
      }
    }



    void Step()
    {
      std::auto_ptr<Orthanc::IDynamicObject>  object(queue_.Dequeue(100));

      if (object.get() != NULL)
      {
        const Item& item = dynamic_cast<Item&>(*object);

        try
        {
          switch (item.GetCommand().GetType())
          {
            case IOracleCommand::Type_OrthancApi:
              Execute(item.GetReceiver(), 
                      dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand()));
              break;

            default:
              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
          }
        }
        catch (Orthanc::OrthancException& e)
        {
          LOG(ERROR) << "Exception within the oracle: " << e.What();
        }
        catch (...)
        {
          LOG(ERROR) << "Native exception within the oracle";
        }
      }
    }


    static void Worker(NativeOracle* that)
    {
      assert(that != NULL);
      
      for (;;)
      {
        {
          boost::mutex::scoped_lock lock(that->mutex_);
          if (that->state_ != State_Running)
          {
            return;
          }
        }

        that->Step();
      }
    }


    void StopInternal()
    {
      {
        boost::mutex::scoped_lock lock(mutex_);

        if (state_ == State_Setup ||
            state_ == State_Stopped)
        {
          return;
        }
        else
        {
          state_ = State_Stopped;
        }
      }

      for (size_t i = 0; i < workers_.size(); i++)
      {
        if (workers_[i] != NULL)
        {
          if (workers_[i]->joinable())
          {
            workers_[i]->join();
          }

          delete workers_[i];
        }
      } 
    }


  public:
    NativeOracle(IMessageEmitter& emitter) :
    emitter_(emitter),
      state_(State_Setup),
      workers_(4)
    {
    }

    virtual ~NativeOracle()
    {
      StopInternal();
    }

    void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
    {
      boost::mutex::scoped_lock lock(mutex_);

      if (state_ != State_Setup)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
      else
      {
        orthanc_ = orthanc;
      }
    }

    void SetWorkersCount(unsigned int count)
    {
      boost::mutex::scoped_lock lock(mutex_);

      if (count <= 0)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
      }
      else if (state_ != State_Setup)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
      else
      {
        workers_.resize(count);
      }
    }

    void Start()
    {
      boost::mutex::scoped_lock lock(mutex_);

      if (state_ != State_Setup)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
      else
      {
        state_ = State_Running;

        for (unsigned int i = 0; i < workers_.size(); i++)
        {
          workers_[i] = new boost::thread(Worker, this);
        }
      }      
    }

    void Stop()
    {
      StopInternal();
    }

    virtual void Schedule(const OrthancStone::IObserver& receiver,
                          IOracleCommand* command)
    {
      queue_.Enqueue(new Item(receiver, command));
    }
  };



  class NativeApplicationContext : public IMessageEmitter
  {
  private:
    boost::shared_mutex            mutex_;
    OrthancStone::MessageBroker    broker_;
    OrthancStone::IObservable      oracleObservable_;

  public:
    NativeApplicationContext() :
      oracleObservable_(broker_)
    {
    }


    virtual void EmitMessage(const OrthancStone::IObserver& observer,
                             const OrthancStone::IMessage& message)
    {
      boost::unique_lock<boost::shared_mutex>  lock(mutex_);
      oracleObservable_.EmitMessage(observer, message);
    }


    class ReaderLock : public boost::noncopyable
    {
    private:
      NativeApplicationContext&                that_;
      boost::shared_lock<boost::shared_mutex>  lock_;

    public:
      ReaderLock(NativeApplicationContext& that) : 
      that_(that),
      lock_(that.mutex_)
      {
      }
    };


    class WriterLock : public boost::noncopyable
    {
    private:
      NativeApplicationContext&                that_;
      boost::unique_lock<boost::shared_mutex>  lock_;

    public:
      WriterLock(NativeApplicationContext& that) : 
      that_(that),
      lock_(that.mutex_)
      {
      }

      OrthancStone::MessageBroker& GetBroker() 
      {
        return that_.broker_;
      }

      OrthancStone::IObservable& GetOracleObservable()
      {
        return that_.oracleObservable_;
      }
    };
  };



  class DicomInstanceParameters : public boost::noncopyable
  {
  private:
    Orthanc::DicomImageInformation    imageInformation_;
    OrthancStone::SopClassUid         sopClassUid_;
    double                            thickness_;
    double                            pixelSpacingX_;
    double                            pixelSpacingY_;
    OrthancStone::CoordinateSystem3D  geometry_;
    OrthancStone::Vector              frameOffsets_;
    bool                              isColor_;
    bool                              hasRescale_;
    double                            rescaleOffset_;
    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 (!OrthancStone::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_;
          }
        }
      }
    }

  public:
    DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
      imageInformation_(dicom)
    {
      if (imageInformation_.GetNumberOfFrames() <= 0)
      {
        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_ = OrthancStone::StringToSopClassUid(s);
      }

      if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
      {
        thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
      }

      OrthancStone::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_ = OrthancStone::CoordinateSystem3D(position, orientation);
      }

      if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
      {
        ComputeDoseOffsets(dicom);
      }

      isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
                  imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);

      double doseGridScaling;

      if (dicom.ParseDouble(rescaleOffset_, 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;
        rescaleOffset_ = 0;
        rescaleSlope_ = doseGridScaling;
      }
      else
      {
        hasRescale_ = false;
      }

      OrthancStone::Vector c, w;
      if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
          OrthancStone::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_ == OrthancStone::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;
      }
    }

    const Orthanc::DicomImageInformation& GetImageInformation() const
    {
      return imageInformation_;
    }

    OrthancStone::SopClassUid GetSopClassUid() const
    {
      return sopClassUid_;
    }

    double GetThickness() const
    {
      return thickness_;
    }

    double GetPixelSpacingX() const
    {
      return pixelSpacingX_;
    }

    double GetPixelSpacingY() const
    {
      return pixelSpacingY_;
    }

    const OrthancStone::CoordinateSystem3D&  GetGeometry() const
    {
      return geometry_;
    }

    OrthancStone::CoordinateSystem3D  GetFrameGeometry(unsigned int frame) const
    {
      if (frame >= imageInformation_.GetNumberOfFrames())
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
      }

      if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
      {
        if (frame >= frameOffsets_.size())
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
        }

        return OrthancStone::CoordinateSystem3D(
          geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
          geometry_.GetAxisX(),
          geometry_.GetAxisY());
      }
    }

    bool FrameContainsPlane(unsigned int frame,
                            const OrthancStone::CoordinateSystem3D& plane) const
    {
      if (frame >= imageInformation_.GetNumberOfFrames())
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
      }

      OrthancStone::CoordinateSystem3D tmp = geometry_;

      if (frame != 0)
      {
        tmp = GetFrameGeometry(frame);
      }

      bool opposite;   // Ignored
      return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(
                opposite, tmp.GetNormal(), plane.GetNormal()) &&
              OrthancStone::LinearAlgebra::IsNear(
                tmp.ProjectAlongNormal(tmp.GetOrigin()),
                tmp.ProjectAlongNormal(plane.GetOrigin()),
                thickness_ / 2.0));
    }

    bool IsColor() const
    {
      return isColor_;
    }

    bool HasRescale() const
    {
      return hasRescale_;
    }

    double GetRescaleOffset() const
    {
      if (hasRescale_)
      {
        return rescaleOffset_;
      }
      else
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
    }

    double GetRescaleSlope() const
    {
      if (hasRescale_)
      {
        return rescaleSlope_;
      }
      else
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
    }

    bool HasDefaultWindowing() const
    {
      return hasDefaultWindowing_;
    }

    float GetDefaultWindowingCenter() const
    {
      if (hasDefaultWindowing_)
      {
        return defaultWindowingCenter_;
      }
      else
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
    }

    float GetDefaultWindowingWidth() const
    {
      if (hasDefaultWindowing_)
      {
        return defaultWindowingWidth_;
      }
      else
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
    }

    Orthanc::PixelFormat GetExpectedPixelFormat() const
    {
      return expectedPixelFormat_;
    }
  };


  class AxialVolumeOrthancLoader : public OrthancStone::IObserver
  {
  private:
    class MessageHandler : public Orthanc::IDynamicObject
    {
    public:
      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const = 0;
    };

    void Handle(const OrthancApiOracleCommand::SuccessMessage& message)
    {
      dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message);
    }


    class LoadSeriesGeometryHandler : public MessageHandler
    {
    private:
      AxialVolumeOrthancLoader&  that_;

    public:
      LoadSeriesGeometryHandler(AxialVolumeOrthancLoader& that) :
      that_(that)
      {
      }

      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
      {
        Json::Value value;
        message.ParseJsonBody(value);

        if (value.type() != Json::objectValue)
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
        }

        Json::Value::Members instances = value.getMemberNames();

        for (size_t i = 0; i < instances.size(); i++)
        {
          Orthanc::DicomMap dicom;
          dicom.FromDicomAsJson(value[instances[i]]);

          DicomInstanceParameters instance(dicom);
        }
      }
    };


    class LoadInstanceGeometryHandler : public MessageHandler
    {
    private:
      AxialVolumeOrthancLoader&  that_;

    public:
      LoadInstanceGeometryHandler(AxialVolumeOrthancLoader& that) :
      that_(that)
      {
      }

      virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
      {
        Json::Value value;
        message.ParseJsonBody(value);

        if (value.type() != Json::objectValue)
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
        }

        Orthanc::DicomMap dicom;
        dicom.FromDicomAsJson(value);

        DicomInstanceParameters instance(dicom);
      }
    };


    bool                                        active_;
    std::auto_ptr<OrthancStone::ImageBuffer3D>  image_;


  public:
    AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) :
      IObserver(oracle.GetBroker()),
      active_(false)
    {
      oracle.RegisterObserverCallback(
        new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage>
        (*this, &AxialVolumeOrthancLoader::Handle));
    }

    void LoadSeries(IOracle& oracle,
                    const std::string& seriesId)
    {
      if (active_)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }

      active_ = true;

      std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
      command->SetUri("/series/" + seriesId + "/instances-tags");
      command->SetPayload(new LoadSeriesGeometryHandler(*this));

      oracle.Schedule(*this, command.release());
    }

    void LoadInstance(IOracle& oracle,
                      const std::string& instanceId)
    {
      if (active_)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }

      active_ = true;

      // Tag "3004-000c" is "Grid Frame Offset Vector", which is
      // mandatory to read RT DOSE, but is too long to be returned by default

      std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
      command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
      command->SetPayload(new LoadInstanceGeometryHandler(*this));

      oracle.Schedule(*this, command.release());
    }
  };

}



class Toto : public OrthancStone::IObserver
{
private:
  void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message)
  {
    Json::Value v;
    message.ParseJsonBody(v);

    printf("ICI [%s]\n", v.toStyledString().c_str());
  }

  void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message)
  {
    printf("ERROR %d\n", message.GetHttpStatus());
  }

public:
  Toto(OrthancStone::IObservable& oracle) :
    IObserver(oracle.GetBroker())
  {
    oracle.RegisterObserverCallback
      (new OrthancStone::Callable
       <Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle));
  }
};


void Run(Refactoring::NativeApplicationContext& context)
{
  std::auto_ptr<Toto> toto;
  std::auto_ptr<Refactoring::AxialVolumeOrthancLoader> loader1, loader2;

  {
    Refactoring::NativeApplicationContext::WriterLock lock(context);
    toto.reset(new Toto(lock.GetOracleObservable()));
    loader1.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable()));
    loader2.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable()));
  }

  Refactoring::NativeOracle oracle(context);

  {
    Orthanc::WebServiceParameters p;
    //p.SetUrl("http://localhost:8043/");
    p.SetCredentials("orthanc", "orthanc");
    oracle.SetOrthancParameters(p);
  }

  oracle.Start();

  {
    Json::Value v = Json::objectValue;
    v["Level"] = "Series";
    v["Query"] = Json::objectValue;

    std::auto_ptr<Refactoring::OrthancApiOracleCommand>  command(new Refactoring::OrthancApiOracleCommand);
    command->SetMethod(Orthanc::HttpMethod_Post);
    command->SetUri("/tools/find");
    command->SetBody(v);

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

  oracle.Stop();
}



/**
 * IMPORTANT: The full arguments to "main()" are needed for SDL on
 * Windows. Otherwise, one gets the linking error "undefined reference
 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
 **/
int main(int argc, char* argv[])
{
  OrthancStone::StoneInitialize();
  Orthanc::Logging::EnableInfoLevel(true);

  try
  {
    Refactoring::NativeApplicationContext context;
    Run(context);
  }
  catch (Orthanc::OrthancException& e)
  {
    LOG(ERROR) << "EXCEPTION: " << e.What();
  }

  OrthancStone::StoneFinalize();

  return 0;
}