view Samples/Sdl/Loader.cpp @ 814:aead999345e0

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 28 May 2019 21:16:39 +0200
parents abc08ac721d3
children df442f1ba0c6
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/>.
 **/


#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h"
#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h"
#include "../../Framework/Oracle/ThreadedOracle.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"

// From Stone
#include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
#include "../../Framework/Loaders/BasicFetchingStrategy.h"
#include "../../Framework/Scene2D/CairoCompositor.h"
#include "../../Framework/Scene2D/Scene2D.h"
#include "../../Framework/Scene2D/PolylineSceneLayer.h"
#include "../../Framework/Scene2D/LookupTableTextureSceneLayer.h"
#include "../../Framework/StoneInitialization.h"
#include "../../Framework/Toolbox/GeometryToolbox.h"
#include "../../Framework/Toolbox/SlicesSorter.h"
#include "../../Framework/Toolbox/DicomStructureSet.h"
#include "../../Framework/Volumes/ImageBuffer3D.h"
#include "../../Framework/Volumes/VolumeImageGeometry.h"
#include "../../Framework/Volumes/VolumeReslicer.h"

// From Orthanc framework
#include <Core/DicomFormat/DicomArray.h>
#include <Core/Images/Image.h>
#include <Core/Images/ImageProcessing.h>
#include <Core/Images/PngWriter.h>
#include <Core/Endianness.h>
#include <Core/Logging.h>
#include <Core/OrthancException.h>
#include <Core/SystemToolbox.h>
#include <Core/Toolbox.h>


#include <EmbeddedResources.h>


namespace OrthancStone
{
  /**
  This class is supplied with Oracle commands and will schedule up to 
  simultaneousDownloads_ of them at the same time, then will schedule the 
  rest once slots become available. It is used, a.o., by the 
  OrtancMultiframeVolumeLoader class.
  */
  class LoaderStateMachine : public IObserver
  {
  protected:
    class State : public Orthanc::IDynamicObject
    {
    private:
      LoaderStateMachine&  that_;

    public:
      State(LoaderStateMachine& that) :
        that_(that)
      {
      }

      State(const State& currentState) :
        that_(currentState.that_)
      {
      }

      void Schedule(OracleCommandWithPayload* command) const
      {
        that_.Schedule(command);
      }

      template <typename T>
      T& GetLoader() const
      {
        return dynamic_cast<T&>(that_);
      }
      
      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
      }
      
      virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
      }
      
      virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
      }
    };

    void Schedule(OracleCommandWithPayload* command)
    {
      std::auto_ptr<OracleCommandWithPayload> protection(command);

      if (command == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
      
      if (!command->HasPayload())
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
                                        "The payload must contain the next state");
      }

      pendingCommands_.push_back(protection.release());
      Step();
    }

    void Start()
    {
      if (active_)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }

      active_ = true;

      for (size_t i = 0; i < simultaneousDownloads_; i++)
      {
        Step();
      }
    }

  private:
    void Step()
    {
      if (!pendingCommands_.empty() &&
          activeCommands_ < simultaneousDownloads_)
      {
        oracle_.Schedule(*this, pendingCommands_.front());
        pendingCommands_.pop_front();

        activeCommands_++;
      }
    }

    void Clear()
    {
      for (PendingCommands::iterator it = pendingCommands_.begin();
           it != pendingCommands_.end(); ++it)
      {
        delete *it;
      }

      pendingCommands_.clear();
    }

    void HandleExceptionMessage(const OracleCommandExceptionMessage& message)
    {
      LOG(ERROR) << "Error in the state machine, stopping all processing";
      Clear();
    }

    template <typename T>
    void HandleSuccessMessage(const T& message)
    {
      assert(activeCommands_ > 0);
      activeCommands_--;

      try
      {
        dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message);
        Step();
      }
      catch (Orthanc::OrthancException& e)
      {
        LOG(ERROR) << "Error in the state machine, stopping all processing: " << e.What();
        Clear();
      }
    }

    typedef std::list<IOracleCommand*>  PendingCommands;

    IOracle&         oracle_;
    bool             active_;
    unsigned int     simultaneousDownloads_;
    PendingCommands  pendingCommands_;
    unsigned int     activeCommands_;

  public:
    LoaderStateMachine(IOracle& oracle,
                       IObservable& oracleObservable) :
      IObserver(oracleObservable.GetBroker()),
      oracle_(oracle),
      active_(false),
      simultaneousDownloads_(4),
      activeCommands_(0)
    {
      oracleObservable.RegisterObserverCallback(
        new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage>
        (*this, &LoaderStateMachine::HandleSuccessMessage));

      oracleObservable.RegisterObserverCallback(
        new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage>
        (*this, &LoaderStateMachine::HandleSuccessMessage));

      oracleObservable.RegisterObserverCallback(
        new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage>
        (*this, &LoaderStateMachine::HandleSuccessMessage));

      oracleObservable.RegisterObserverCallback(
        new Callable<LoaderStateMachine, OracleCommandExceptionMessage>
        (*this, &LoaderStateMachine::HandleExceptionMessage));
    }

    virtual ~LoaderStateMachine()
    {
      Clear();
    }

    bool IsActive() const
    {
      return active_;
    }

    void SetSimultaneousDownloads(unsigned int count)
    {
      if (active_)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
      else if (count == 0)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
      }
      else
      {
        simultaneousDownloads_ = count;
      }
    }
  };



  class OrthancMultiframeVolumeLoader :
    public LoaderStateMachine,
    public IObservable
  {
  private:
    class LoadRTDoseGeometry : public State
    {
    private:
      std::auto_ptr<Orthanc::DicomMap>  dicom_;

    public:
      LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that,
                         Orthanc::DicomMap* dicom) :
        State(that),
        dicom_(dicom)
      {
        if (dicom == NULL)
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
        }

      }

      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        // Complete the DICOM tags with just-received "Grid Frame Offset Vector"
        std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
        dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);

        GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
      }      
    };


    static std::string GetSopClassUid(const Orthanc::DicomMap& dicom)
    {
      std::string s;
      if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                        "DICOM file without SOP class UID");
      }
      else
      {
        return s;
      }
    }
    

    class LoadGeometry : public State
    {
    public:
      LoadGeometry(OrthancMultiframeVolumeLoader& that) :
        State(that)
      {
      }
      
      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
        
        Json::Value body;
        message.ParseJsonBody(body);
        
        if (body.type() != Json::objectValue)
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
        }

        std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap);
        dicom->FromDicomAsJson(body);

        if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose)
        {
          // Download the "Grid Frame Offset Vector" DICOM tag, that is
          // mandatory for RT-DOSE, but is too long to be returned by default
          
          std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
          command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
                          Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
          command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release()));

          Schedule(command.release());
        }
        else
        {
          loader.SetGeometry(*dicom);
        }
      }
    };



    class LoadTransferSyntax : public State
    {
    public:
      LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) :
        State(that)
      {
      }
      
      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
      }
    };
   
    
    class LoadUncompressedPixelData : public State
    {
    public:
      LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) :
        State(that)
      {
      }
      
      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
      }
    };
   
    

    boost::shared_ptr<DicomVolumeImage>   volume_;
    std::string  instanceId_;
    std::string  transferSyntaxUid_;


    const std::string& GetInstanceId() const
    {
      if (IsActive())
      {
        return instanceId_;
      }
      else
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
    }


    void ScheduleFrameDownloads()
    {
      if (transferSyntaxUid_.empty() ||
          !volume_->HasGeometry())
      {
        return;
      }
      /*
      1.2.840.10008.1.2	Implicit VR Endian: Default Transfer Syntax for DICOM
      1.2.840.10008.1.2.1	Explicit VR Little Endian
      1.2.840.10008.1.2.2	Explicit VR Big Endian

      See https://www.dicomlibrary.com/dicom/transfer-syntax/
      */
      if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
          transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
          transferSyntaxUid_ == "1.2.840.10008.1.2.2")
      {
        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
        command->SetHttpHeader("Accept-Encoding", "gzip");
        command->SetUri("/instances/" + instanceId_ + "/content/" +
                        Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
        command->SetPayload(new LoadUncompressedPixelData(*this));
        Schedule(command.release());
      }
      else
      {
        throw Orthanc::OrthancException(
          Orthanc::ErrorCode_NotImplemented,
          "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_);
      }
    }
      

    void SetTransferSyntax(const std::string& transferSyntax)
    {
      transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax);
      ScheduleFrameDownloads();
    }
    

    void SetGeometry(const Orthanc::DicomMap& dicom)
    {
      DicomInstanceParameters parameters(dicom);
      volume_->SetDicomParameters(parameters);
      
      Orthanc::PixelFormat format;
      if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
      }

      double spacingZ;
      switch (parameters.GetSopClassUid())
      {
        case SopClassUid_RTDose:
          spacingZ = parameters.GetThickness();
          break;

        default:
          throw Orthanc::OrthancException(
            Orthanc::ErrorCode_NotImplemented,
            "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom));
      }

      const unsigned int width = parameters.GetImageInformation().GetWidth();
      const unsigned int height = parameters.GetImageInformation().GetHeight();
      const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();

      {
        VolumeImageGeometry geometry;
        geometry.SetSize(width, height, depth);
        geometry.SetAxialGeometry(parameters.GetGeometry());
        geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
                                    parameters.GetPixelSpacingY(), spacingZ);
        volume_->Initialize(geometry, format);
      }

      volume_->GetPixelData().Clear();

      ScheduleFrameDownloads();

      BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
    }


    ORTHANC_FORCE_INLINE
    static void CopyPixel(uint32_t& target,
                          const void* source)
    {
      // TODO - check alignement?
      target = le32toh(*reinterpret_cast<const uint32_t*>(source));
    }
      

    template <typename T>
    void CopyPixelData(const std::string& pixelData)
    {
      ImageBuffer3D& target = volume_->GetPixelData();
      
      const unsigned int bpp = target.GetBytesPerPixel();
      const unsigned int width = target.GetWidth();
      const unsigned int height = target.GetHeight();
      const unsigned int depth = target.GetDepth();

      if (pixelData.size() != bpp * width * height * depth)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                        "The pixel data has not the proper size");
      }

      if (pixelData.empty())
      {
        return;
      }

      const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());

      for (unsigned int z = 0; z < depth; z++)
      {
        ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z);

        assert (writer.GetAccessor().GetWidth() == width &&
                writer.GetAccessor().GetHeight() == height);

        for (unsigned int y = 0; y < height; y++)
        {
          assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));

          T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));

          for (unsigned int x = 0; x < width; x++)
          {
            CopyPixel(*target, source);
            
            target ++;
            source += bpp;
          }
        }
      }
    }
    

    void SetUncompressedPixelData(const std::string& pixelData)
    {
      switch (volume_->GetPixelData().GetFormat())
      {
        case Orthanc::PixelFormat_Grayscale32:
          CopyPixelData<uint32_t>(pixelData);
          break;

        default:
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
      }

      volume_->IncrementRevision();

      BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
    }


  public:
    OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
                                  IOracle& oracle,
                                  IObservable& oracleObservable) :
      LoaderStateMachine(oracle, oracleObservable),
      IObservable(oracleObservable.GetBroker()),
      volume_(volume)
    {
      if (volume.get() == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
    }


    void LoadInstance(const std::string& instanceId)
    {
      Start();

      instanceId_ = instanceId;

      {
        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
        command->SetHttpHeader("Accept-Encoding", "gzip");
        command->SetUri("/instances/" + instanceId + "/tags");
        command->SetPayload(new LoadGeometry(*this));
        Schedule(command.release());
      }

      {
        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
        command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
        command->SetPayload(new LoadTransferSyntax(*this));
        Schedule(command.release());
      }
    }
  };


  /**
  This class is able to supply an extract slice for an arbitrary cutting
  plane through a volume image
  */
  class DicomVolumeImageReslicer : public IVolumeSlicer
  {
  private:
    class Slice : public IExtractedSlice
    {
    private:
      DicomVolumeImageReslicer&  that_;
      CoordinateSystem3D         cuttingPlane_;
      
    public:
      Slice(DicomVolumeImageReslicer& that,
            const CoordinateSystem3D& cuttingPlane) :
        that_(that),
        cuttingPlane_(cuttingPlane)
      {
      }
      
      virtual bool IsValid()
      {
        return true;
      }

      virtual uint64_t GetRevision()
      {
        return that_.volume_->GetRevision();
      }

      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
                                            const CoordinateSystem3D& cuttingPlane)
      {
        VolumeReslicer& reslicer = that_.reslicer_;
        
        if (configurator == NULL)
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
                                          "Must provide a layer style configurator");
        }
        
        reslicer.SetOutputFormat(that_.volume_->GetPixelData().GetFormat());
        reslicer.Apply(that_.volume_->GetPixelData(),
                       that_.volume_->GetGeometry(),
                       cuttingPlane);

        if (reslicer.IsSuccess())
        {
          std::auto_ptr<TextureBaseSceneLayer> layer
            (configurator->CreateTextureFromDicom(reslicer.GetOutputSlice(),
                                                  that_.volume_->GetDicomParameters()));
          if (layer.get() == NULL)
          {
            return NULL;
          }

          double s = reslicer.GetPixelSpacing();
          layer->SetPixelSpacing(s, s);
          layer->SetOrigin(reslicer.GetOutputExtent().GetX1() + 0.5 * s,
                           reslicer.GetOutputExtent().GetY1() + 0.5 * s);

          // TODO - Angle!!
                           
          return layer.release();
        }
        else
        {
          return NULL;
        }          
      }
    };
    
    boost::shared_ptr<DicomVolumeImage>  volume_;
    VolumeReslicer                       reslicer_;

  public:
    DicomVolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume) :
      volume_(volume)
    {
      if (volume.get() == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
    }

    ImageInterpolation GetInterpolation() const
    {
      return reslicer_.GetInterpolation();
    }

    void SetInterpolation(ImageInterpolation interpolation)
    {
      reslicer_.SetInterpolation(interpolation);
    }

    bool IsFastMode() const
    {
      return reslicer_.IsFastMode();
    }

    void SetFastMode(bool fast)
    {
      reslicer_.EnableFastMode(fast);
    }
    
    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
    {
      if (volume_->HasGeometry())
      {
        return new Slice(*this, cuttingPlane);
      }
      else
      {
        return new IVolumeSlicer::InvalidSlice;
      }
    }
  };



  class DicomStructureSetLoader :
    public LoaderStateMachine,
    public IVolumeSlicer
  {
  private:
    class AddReferencedInstance : public State
    {
    private:
      std::string instanceId_;
      
    public:
      AddReferencedInstance(DicomStructureSetLoader& that,
                            const std::string& instanceId) :
        State(that),
        instanceId_(instanceId)
      {
      }

      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        Json::Value tags;
        message.ParseJsonBody(tags);
        
        Orthanc::DicomMap dicom;
        dicom.FromDicomAsJson(tags);

        DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
        loader.content_->AddReferencedSlice(dicom);

        loader.countProcessedInstances_ ++;
        assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_);

        if (loader.countProcessedInstances_ == loader.countReferencedInstances_)
        {
          // All the referenced instances have been loaded, finalize the RT-STRUCT
          loader.content_->CheckReferencedSlices();
          loader.revision_++;
        }
      }
    };
    
    // State that converts a "SOP Instance UID" to an Orthanc identifier
    class LookupInstance : public State
    {
    private:
      std::string  sopInstanceUid_;
      
    public:
      LookupInstance(DicomStructureSetLoader& that,
                     const std::string& sopInstanceUid) :
        State(that),
        sopInstanceUid_(sopInstanceUid)
      {
      }

      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();

        Json::Value lookup;
        message.ParseJsonBody(lookup);

        if (lookup.type() != Json::arrayValue ||
            lookup.size() != 1 ||
            !lookup[0].isMember("Type") ||
            !lookup[0].isMember("Path") ||
            lookup[0]["Type"].type() != Json::stringValue ||
            lookup[0]["ID"].type() != Json::stringValue ||
            lookup[0]["Type"].asString() != "Instance")
        {
          throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);          
        }

        const std::string instanceId = lookup[0]["ID"].asString();

        {
          std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
          command->SetHttpHeader("Accept-Encoding", "gzip");
          command->SetUri("/instances/" + instanceId + "/tags");
          command->SetPayload(new AddReferencedInstance(loader, instanceId));
          Schedule(command.release());
        }
      }
    };
    
    class LoadStructure : public State
    {
    public:
      LoadStructure(DicomStructureSetLoader& that) :
        State(that)
      {
      }

      virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
      {
        DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
        
        {
          OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
          loader.content_.reset(new DicomStructureSet(dicom));
        }

        std::set<std::string> instances;
        loader.content_->GetReferencedInstances(instances);

        loader.countReferencedInstances_ = instances.size();

        for (std::set<std::string>::const_iterator
               it = instances.begin(); it != instances.end(); ++it)
        {
          std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
          command->SetUri("/tools/lookup");
          command->SetMethod(Orthanc::HttpMethod_Post);
          command->SetBody(*it);
          command->SetPayload(new LookupInstance(loader, *it));
          Schedule(command.release());
        }
      }
    };


    
    std::auto_ptr<DicomStructureSet>  content_;
    uint64_t                          revision_;
    std::string                       instanceId_;
    unsigned int                      countProcessedInstances_;
    unsigned int                      countReferencedInstances_;

    
    class Slice : public IExtractedSlice
    {
    private:
      const DicomStructureSet&  content_;
      uint64_t                  revision_;
      bool                      isValid_;
      
    public:
      Slice(const DicomStructureSet& content,
            uint64_t revision,
            const CoordinateSystem3D& cuttingPlane) :
        content_(content),
        revision_(revision)
      {
        bool opposite;

        const Vector normal = content.GetNormal();
        isValid_ = (
          GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
          GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
          GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
      }
      
      virtual bool IsValid()
      {
        return isValid_;
      }

      virtual uint64_t GetRevision()
      {
        return revision_;
      }

      virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
                                            const CoordinateSystem3D& cuttingPlane)
      {
        assert(isValid_);

        std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
        layer->SetThickness(2);

        for (size_t i = 0; i < content_.GetStructuresCount(); i++)
        {
          const Color& color = content_.GetStructureColor(i);

          std::vector< std::vector<DicomStructureSet::PolygonPoint> > polygons;
          
          if (content_.ProjectStructure(polygons, i, cuttingPlane))
          {
            for (size_t j = 0; j < polygons.size(); j++)
            {
              PolylineSceneLayer::Chain chain;
              chain.resize(polygons[j].size());
            
              for (size_t k = 0; k < polygons[j].size(); k++)
              {
                chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second);
              }

              layer->AddChain(chain, true /* closed */, color);
            }
          }
        }

        return layer.release();
      }
    };
    
  public:
    DicomStructureSetLoader(IOracle& oracle,
                            IObservable& oracleObservable) :
      LoaderStateMachine(oracle, oracleObservable),
      revision_(0),
      countProcessedInstances_(0),
      countReferencedInstances_(0)
    {
    }
    
    
    void LoadInstance(const std::string& instanceId)
    {
      Start();
      
      instanceId_ = instanceId;
      
      {
        std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
        command->SetHttpHeader("Accept-Encoding", "gzip");
        command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3006-0050");
        command->SetPayload(new LoadStructure(*this));
        Schedule(command.release());
      }
    }

    virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
    {
      if (content_.get() == NULL)
      {
        // Geometry is not available yet
        return new IVolumeSlicer::InvalidSlice;
      }
      else
      {
        return new Slice(*content_, revision_, cuttingPlane);
      }
    }
  };



  class VolumeSceneLayerSource : public boost::noncopyable
  {
  private:
    Scene2D&                                scene_;
    int                                     layerDepth_;
    boost::shared_ptr<IVolumeSlicer>        slicer_;
    std::auto_ptr<ILayerStyleConfigurator>  configurator_;
    std::auto_ptr<CoordinateSystem3D>       lastPlane_;
    uint64_t                                lastRevision_;
    uint64_t                                lastConfiguratorRevision_;

    static bool IsSameCuttingPlane(const CoordinateSystem3D& a,
                                   const CoordinateSystem3D& b)
    {
      // TODO - What if the normal is reversed?
      double distance;
      return (CoordinateSystem3D::ComputeDistance(distance, a, b) &&
              LinearAlgebra::IsCloseToZero(distance));
    }

    void ClearLayer()
    {
      scene_.DeleteLayer(layerDepth_);
      lastPlane_.reset(NULL);
    }

  public:
    VolumeSceneLayerSource(Scene2D& scene,
                           int layerDepth,
                           const boost::shared_ptr<IVolumeSlicer>& slicer) :
      scene_(scene),
      layerDepth_(layerDepth),
      slicer_(slicer)
    {
      if (slicer == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
    }

    const IVolumeSlicer& GetSlicer() const
    {
      return *slicer_;
    }

    void RemoveConfigurator()
    {
      configurator_.reset();
      lastPlane_.reset();
    }

    void SetConfigurator(ILayerStyleConfigurator* configurator)  // Takes ownership
    {
      if (configurator == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }

      configurator_.reset(configurator);

      // Invalidate the layer
      lastPlane_.reset(NULL);
    }

    bool HasConfigurator() const
    {
      return configurator_.get() != NULL;
    }

    ILayerStyleConfigurator& GetConfigurator() const
    {
      if (configurator_.get() == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }
      
      return *configurator_;
    }

    void Update(const CoordinateSystem3D& plane)
    {
      assert(slicer_.get() != NULL);
      std::auto_ptr<IVolumeSlicer::IExtractedSlice> slice(slicer_->ExtractSlice(plane));

      if (slice.get() == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);        
      }

      if (!slice->IsValid())
      {
        // The slicer cannot handle this cutting plane: Clear the layer
        ClearLayer();
      }
      else if (lastPlane_.get() != NULL &&
               IsSameCuttingPlane(*lastPlane_, plane) &&
               lastRevision_ == slice->GetRevision())
      {
        // The content of the slice has not changed: Don't update the
        // layer content, but possibly update its style

        if (configurator_.get() != NULL &&
            configurator_->GetRevision() != lastConfiguratorRevision_ &&
            scene_.HasLayer(layerDepth_))
        {
          configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));
        }
      }
      else
      {
        // Content has changed: An update is needed
        lastPlane_.reset(new CoordinateSystem3D(plane));
        lastRevision_ = slice->GetRevision();

        std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane));
        if (layer.get() == NULL)
        {
          ClearLayer();
        }
        else
        {
          if (configurator_.get() != NULL)
          {
            lastConfiguratorRevision_ = configurator_->GetRevision();
            configurator_->ApplyStyle(*layer);
          }

          scene_.SetLayer(layerDepth_, layer.release());
        }
      }
    }
  };





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

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


    virtual void EmitMessage(const IObserver& observer,
                             const IMessage& message)
    {
      try
      {
        boost::unique_lock<boost::shared_mutex>  lock(mutex_);
        oracleObservable_.EmitMessage(observer, message);
      }
      catch (Orthanc::OrthancException& e)
      {
        LOG(ERROR) << "Exception while emitting a message: " << e.What();
      }
    }


    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_)
      {
      }

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

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



class Toto : public OrthancStone::IObserver
{
private:
  OrthancStone::CoordinateSystem3D  plane_;
  OrthancStone::IOracle&            oracle_;
  OrthancStone::Scene2D             scene_;
  std::auto_ptr<OrthancStone::VolumeSceneLayerSource>  source1_, source2_, source3_;


  void Refresh()
  {
    if (source1_.get() != NULL)
    {
      source1_->Update(plane_);
    }
      
    if (source2_.get() != NULL)
    {
      source2_->Update(plane_);
    }

    if (source3_.get() != NULL)
    {
      source3_->Update(plane_);
    }

    scene_.FitContent(1024, 768);
      
    {
      OrthancStone::CairoCompositor compositor(scene_, 1024, 768);
      compositor.Refresh();
        
      Orthanc::ImageAccessor accessor;
      compositor.GetCanvas().GetReadOnlyAccessor(accessor);

      Orthanc::Image tmp(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false);
      Orthanc::ImageProcessing::Convert(tmp, accessor);
        
      static unsigned int count = 0;
      char buf[64];
      sprintf(buf, "scene-%06d.png", count++);
        
      Orthanc::PngWriter writer;
      writer.WriteToFile(buf, tmp);
    }
  }

  
  void Handle(const OrthancStone::DicomVolumeImage::GeometryReadyMessage& message)
  {
    printf("Geometry ready\n");
    
    //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry();
    //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry();
    plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry();
    plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f));

    Refresh();
  }
  
  
  void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message)
  {
    if (message.GetOrigin().HasPayload())
    {
      printf("TIMEOUT! %d\n", dynamic_cast<const Orthanc::SingleValueObject<unsigned int>& >(message.GetOrigin().GetPayload()).GetValue());
    }
    else
    {
      printf("TIMEOUT\n");

      Refresh();

      /**
       * The sleep() leads to a crash if the oracle is still running,
       * while this object is destroyed. Always stop the oracle before
       * destroying active objects.  (*)
       **/
      // boost::this_thread::sleep(boost::posix_time::seconds(2));

      oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(message.GetOrigin().GetDelay()));
    }
  }

  void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
  {
    Json::Value v;
    message.ParseJsonBody(v);

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

  void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message)
  {
    printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
  }

  void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
  {
    printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
  }

  void Handle(const OrthancStone::OracleCommandExceptionMessage& message)
  {
    printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType());

    switch (message.GetCommand().GetType())
    {
      case OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg:
        printf("URI: [%s]\n", dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&>
               (message.GetCommand()).GetUri().c_str());
        break;
      
      default:
        break;
    }
  }

public:
  Toto(OrthancStone::IOracle& oracle,
       OrthancStone::IObservable& oracleObservable) :
    IObserver(oracleObservable.GetBroker()),
    oracle_(oracle)
  {
    oracleObservable.RegisterObserverCallback
      (new OrthancStone::Callable
       <Toto, OrthancStone::SleepOracleCommand::TimeoutMessage>(*this, &Toto::Handle));

    oracleObservable.RegisterObserverCallback
      (new OrthancStone::Callable
       <Toto, OrthancStone::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));

    oracleObservable.RegisterObserverCallback
      (new OrthancStone::Callable
       <Toto, OrthancStone::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));

    oracleObservable.RegisterObserverCallback
      (new OrthancStone::Callable
       <Toto, OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));

    oracleObservable.RegisterObserverCallback
      (new OrthancStone::Callable
       <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle));
  }

  void SetReferenceLoader(OrthancStone::IObservable& loader)
  {
    loader.RegisterObserverCallback
      (new OrthancStone::Callable
       <Toto, OrthancStone::DicomVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle));
  }

  void SetVolume1(int depth,
                  const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
                  OrthancStone::ILayerStyleConfigurator* style)
  {
    source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));

    if (style != NULL)
    {
      source1_->SetConfigurator(style);
    }
  }

  void SetVolume2(int depth,
                  const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
                  OrthancStone::ILayerStyleConfigurator* style)
  {
    source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));

    if (style != NULL)
    {
      source2_->SetConfigurator(style);
    }
  }

  void SetStructureSet(int depth,
                       const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
  {
    source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
  }
                       
};


void Run(OrthancStone::NativeApplicationContext& context,
         OrthancStone::ThreadedOracle& oracle)
{
  boost::shared_ptr<OrthancStone::DicomVolumeImage>  ct(new OrthancStone::DicomVolumeImage);
  boost::shared_ptr<OrthancStone::DicomVolumeImage>  dose(new OrthancStone::DicomVolumeImage);

  
  boost::shared_ptr<Toto> toto;
  boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader;
  boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> doseLoader;
  boost::shared_ptr<OrthancStone::DicomStructureSetLoader>  rtstructLoader;

  {
    OrthancStone::NativeApplicationContext::WriterLock lock(context);
    toto.reset(new Toto(oracle, lock.GetOracleObservable()));
    ctLoader.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct, oracle, lock.GetOracleObservable()));
    doseLoader.reset(new OrthancStone::OrthancMultiframeVolumeLoader(dose, oracle, lock.GetOracleObservable()));
    rtstructLoader.reset(new OrthancStone::DicomStructureSetLoader(oracle, lock.GetOracleObservable()));
  }


  //toto->SetReferenceLoader(*ctLoader);
  toto->SetReferenceLoader(*doseLoader);


#if 1
  toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator);
#else
  {
    boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::DicomVolumeImageReslicer(ct));
    toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator);
  }
#endif  
  

  {
    std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator);
    config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);

    boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> tmp(new OrthancStone::DicomVolumeImageMPRSlicer(dose));
    toto->SetVolume2(1, tmp, config.release());
  }

  toto->SetStructureSet(2, rtstructLoader);
  
  oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100));

  if (0)
  {
    Json::Value v = Json::objectValue;
    v["Level"] = "Series";
    v["Query"] = Json::objectValue;

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

    oracle.Schedule(*toto, command.release());
  }
  
  if (0)
  {
    std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
    oracle.Schedule(*toto, command.release());
  }
  
  if (0)
  {
    std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
    oracle.Schedule(*toto, command.release());
  }
  
  if (0)
  {
    std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
    oracle.Schedule(*toto, command.release());
  }
  
  if (0)
  {
    std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
    command->SetHttpHeader("Accept-Encoding", "gzip");
    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
    oracle.Schedule(*toto, command.release());
  }
  
  if (0)
  {
    std::auto_ptr<OrthancStone::GetOrthancImageCommand>  command(new OrthancStone::GetOrthancImageCommand);
    command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
    command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
    oracle.Schedule(*toto, command.release());
  }

  if (0)
  {
    std::auto_ptr<OrthancStone::GetOrthancWebViewerJpegCommand>  command(new OrthancStone::GetOrthancWebViewerJpegCommand);
    command->SetHttpHeader("Accept-Encoding", "gzip");
    command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
    command->SetQuality(90);
    oracle.Schedule(*toto, command.release());
  }


  if (0)
  {
    for (unsigned int i = 0; i < 10; i++)
    {
      std::auto_ptr<OrthancStone::SleepOracleCommand> command(new OrthancStone::SleepOracleCommand(i * 1000));
      command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(42 * i));
      oracle.Schedule(*toto, command.release());
    }
  }

  
  // 2017-11-17-Anonymized
#if 0
  // BGO data
  ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
  doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb");  // RT-DOSE
  //rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
#else
  //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618");  // CT
  //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6");  // RT-DOSE
  //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6");  // RT-STRUCT

  // 2017-05-16
  ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa");  // CT
  doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad");  // RT-DOSE
  rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9");  // RT-STRUCT
#endif
  // 2015-01-28-Multiframe
  //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279");  // Multiframe CT
  
  // Delphine
  //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e");  // CT
  //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5");  // Lung 1/10mm


  {
    LOG(WARNING) << "...Waiting for Ctrl-C...";

    oracle.Start();

    Orthanc::SystemToolbox::ServerBarrier();

    /**
     * WARNING => The oracle must be stopped BEFORE the objects using
     * it are destroyed!!! This forces to wait for the completion of
     * the running callback methods. Otherwise, the callbacks methods
     * might still be running while their parent object is destroyed,
     * resulting in crashes. This is very visible if adding a sleep(),
     * as in (*).
     **/

    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
  {
    OrthancStone::NativeApplicationContext context;

    OrthancStone::ThreadedOracle oracle(context);
    //oracle.SetThreadsCount(1);

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

    //oracle.Start();

    Run(context, oracle);
    
    //oracle.Stop();
  }
  catch (Orthanc::OrthancException& e)
  {
    LOG(ERROR) << "EXCEPTION: " << e.What();
  }

  OrthancStone::StoneFinalize();

  return 0;
}