changeset 5:798076adf9e9

rename
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 02 Jun 2015 10:11:34 +0200
parents 2e999f3e84b4
children c584c25a74fd
files CppClient/ArrayFilledByThreads.cpp CppClient/ArrayFilledByThreads.h CppClient/Instance.cpp CppClient/Instance.h CppClient/OrthancClientException.h CppClient/OrthancConnection.cpp CppClient/OrthancConnection.h CppClient/Patient.cpp CppClient/Patient.h CppClient/Series.cpp CppClient/Series.h CppClient/Study.cpp CppClient/Study.h CppClient/ThreadedCommandProcessor.cpp CppClient/ThreadedCommandProcessor.h OrthancCppClient/ArrayFilledByThreads.cpp OrthancCppClient/ArrayFilledByThreads.h OrthancCppClient/Instance.cpp OrthancCppClient/Instance.h OrthancCppClient/OrthancClientException.h OrthancCppClient/OrthancConnection.cpp OrthancCppClient/OrthancConnection.h OrthancCppClient/Patient.cpp OrthancCppClient/Patient.h OrthancCppClient/Series.cpp OrthancCppClient/Series.h OrthancCppClient/Study.cpp OrthancCppClient/Study.h OrthancCppClient/ThreadedCommandProcessor.cpp OrthancCppClient/ThreadedCommandProcessor.h
diffstat 30 files changed, 2558 insertions(+), 2558 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/ArrayFilledByThreads.cpp	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,153 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ArrayFilledByThreads.h"
+
+#include "ThreadedCommandProcessor.h"
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace Orthanc
+{
+  class ArrayFilledByThreads::Command : public ICommand
+  {
+  private:
+    ArrayFilledByThreads&  that_;
+    size_t  index_;
+
+  public:
+    Command(ArrayFilledByThreads& that,
+            size_t index) :
+      that_(that),
+      index_(index)
+    {
+    }
+
+    virtual bool Execute()
+    {
+      std::auto_ptr<IDynamicObject> obj(that_.filler_.GetFillerItem(index_));
+      if (obj.get() == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        boost::mutex::scoped_lock lock(that_.mutex_);
+        that_.array_[index_] = obj.release();
+        return true;
+      }
+    }
+  };
+
+  void ArrayFilledByThreads::Clear()
+  {
+    for (size_t i = 0; i < array_.size(); i++)
+    {
+      if (array_[i])
+        delete array_[i];
+    }
+
+    array_.clear();
+    filled_ = false;
+  }
+
+  void ArrayFilledByThreads::Update()
+  {
+    if (!filled_)
+    {
+      array_.resize(filler_.GetFillerSize());
+
+      Orthanc::ThreadedCommandProcessor processor(threadCount_);
+      for (size_t i = 0; i < array_.size(); i++)
+      {
+        processor.Post(new Command(*this, i));
+      }
+
+      processor.Join();
+      filled_ = true;
+    }
+  }
+
+
+  ArrayFilledByThreads::ArrayFilledByThreads(IFiller& filler) : filler_(filler)
+  {
+    filled_ = false;
+    threadCount_ = 4;
+  }
+
+
+  ArrayFilledByThreads::~ArrayFilledByThreads()
+  {
+    Clear();
+  }
+
+  
+  void ArrayFilledByThreads::Reload()
+  {
+    Clear();
+    Update();
+  }
+
+
+  void ArrayFilledByThreads::Invalidate()
+  {
+    Clear();
+  }
+
+
+  void ArrayFilledByThreads::SetThreadCount(unsigned int t)
+  {
+    if (t < 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    threadCount_ = t;
+  }
+
+
+  size_t ArrayFilledByThreads::GetSize()
+  {
+    Update();
+    return array_.size();
+  }
+
+
+  IDynamicObject& ArrayFilledByThreads::GetItem(size_t index)
+  {
+    if (index >= GetSize())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    return *array_[index];
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/ArrayFilledByThreads.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/thread.hpp>
+
+#include "../Orthanc/Core/IDynamicObject.h"
+
+namespace OrthancClient
+{
+  class ArrayFilledByThreads
+  {
+  public:
+    class IFiller
+    {
+    public:
+      virtual size_t GetFillerSize() = 0;
+
+      virtual IDynamicObject* GetFillerItem(size_t index) = 0;
+    };
+
+  private:
+    IFiller& filler_;
+    boost::mutex  mutex_;
+    std::vector<IDynamicObject*>  array_;
+    bool filled_;
+    unsigned int threadCount_;
+
+    class Command;
+
+    void Clear();
+
+    void Update();
+
+  public:
+    ArrayFilledByThreads(IFiller& filler);
+
+    ~ArrayFilledByThreads();
+  
+    void Reload();
+
+    void Invalidate();
+
+    void SetThreadCount(unsigned int t);
+
+    unsigned int GetThreadCount() const
+    {
+      return threadCount_;
+    }
+
+    size_t GetSize();
+
+    IDynamicObject& GetItem(size_t index);
+  };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Instance.cpp	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,285 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Instance.h"
+
+#include "OrthancConnection.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancClient
+{
+  void Instance::DownloadImage()
+  {
+    if (reader_.get() == NULL)
+    {
+      const char* suffix;
+      switch (mode_)
+      {
+        case Orthanc::ImageExtractionMode_Preview:
+          suffix = "preview";
+          break;
+          
+        case Orthanc::ImageExtractionMode_UInt8:
+          suffix = "image-uint8";
+          break;
+          
+        case Orthanc::ImageExtractionMode_UInt16:
+          suffix = "image-uint16";
+          break;
+          
+        case Orthanc::ImageExtractionMode_Int16:
+          suffix = "image-int16";
+          break;
+          
+        default:
+          throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      Orthanc::HttpClient client(connection_.GetHttpClient());
+      client.SetUrl(std::string(connection_.GetOrthancUrl()) +  "/instances/" + id_ + "/" + suffix);
+      std::string png;
+
+      if (!client.Apply(png))
+      {
+        throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
+      }
+     
+      reader_.reset(new Orthanc::PngReader);
+      reader_->ReadFromMemory(png);
+    }
+  }
+
+  void Instance::DownloadDicom()
+  {
+    if (dicom_.get() == NULL)
+    {
+      Orthanc::HttpClient client(connection_.GetHttpClient());
+      client.SetUrl(std::string(connection_.GetOrthancUrl()) +  "/instances/" + id_ + "/file");
+
+      dicom_.reset(new std::string);
+
+      if (!client.Apply(*dicom_))
+      {
+        throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+    }
+  }
+
+  Instance::Instance(const OrthancConnection& connection,
+                     const char* id) :
+    connection_(connection),
+    id_(id),
+    mode_(Orthanc::ImageExtractionMode_Int16)
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+            
+    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/simplified-tags");
+    Json::Value v;
+    if (!client.Apply(tags_))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  const char* Instance::GetTagAsString(const char* tag) const
+  {
+    if (tags_.isMember(tag))
+    {
+      return tags_[tag].asCString();
+    }
+    else
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_InexistentItem);
+    }
+  }
+
+  float Instance::GetTagAsFloat(const char* tag) const
+  {
+    std::string value = GetTagAsString(tag);
+
+    try
+    {
+      return boost::lexical_cast<float>(value);
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+  int Instance::GetTagAsInt(const char* tag) const
+  {
+    std::string value = GetTagAsString(tag);
+
+    try
+    {
+      return boost::lexical_cast<int>(value);
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+  unsigned int Instance::GetWidth()
+  {
+    DownloadImage();
+    return reader_->GetWidth();
+  }
+
+  unsigned int Instance::GetHeight() 
+  {
+    DownloadImage();
+    return reader_->GetHeight();
+  }
+
+  unsigned int Instance::GetPitch()
+  {
+    DownloadImage();
+    return reader_->GetPitch();
+  }
+
+  Orthanc::PixelFormat Instance::GetPixelFormat()
+  {
+    DownloadImage();
+    return reader_->GetFormat();
+  }
+
+  const void* Instance::GetBuffer()
+  {
+    DownloadImage();
+    return reader_->GetConstBuffer();
+  }
+
+  const void* Instance::GetBuffer(unsigned int y)
+  {
+    DownloadImage();
+    return reader_->GetConstRow(y);
+  }
+
+  void Instance::DiscardImage()
+  {
+    reader_.reset();
+  }
+
+  void Instance::DiscardDicom()
+  {
+    dicom_.reset();
+  }
+
+
+  void Instance::SetImageExtractionMode(Orthanc::ImageExtractionMode mode)
+  {
+    if (mode_ == mode)
+    {
+      return;
+    }
+
+    DiscardImage();
+    mode_ = mode;
+  }
+
+
+  void Instance::SplitVectorOfFloats(std::vector<float>& target,
+                                     const char* tag)
+  {
+    const std::string value = GetTagAsString(tag);
+
+    target.clear();
+
+    try
+    {
+      std::string tmp;
+      for (size_t i = 0; i < value.size(); i++)
+      {
+        if (value[i] == '\\')
+        {
+          target.push_back(boost::lexical_cast<float>(tmp));
+          tmp.clear();
+        }
+        else
+        {
+          tmp.push_back(value[i]);
+        }
+      }
+
+      target.push_back(boost::lexical_cast<float>(tmp));
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      // Unable to parse the Image Orientation Patient.
+      throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  const uint64_t Instance::GetDicomSize()
+  {
+    DownloadDicom();
+    assert(dicom_.get() != NULL);
+    return dicom_->size();
+  }
+
+  const void* Instance::GetDicom()
+  {
+    DownloadDicom();
+    assert(dicom_.get() != NULL);
+
+    if (dicom_->size() == 0)
+    {
+      return NULL;
+    }
+    else
+    {
+      return &((*dicom_) [0]);
+    }
+  }
+
+
+  void Instance::LoadTagContent(const char* path)
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/content/" + path);
+
+    if (!client.Apply(content_))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_UnknownResource);
+    }
+  }
+
+
+  const char* Instance::GetLoadedTagContent() const
+  {
+    return content_.c_str();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Instance.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,202 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <json/value.h>
+
+#include "OrthancClientException.h"
+#include "../Orthanc/Core/IDynamicObject.h"
+#include "../Orthanc/Core/ImageFormats/PngReader.h"
+
+namespace OrthancClient
+{
+  class OrthancConnection;
+
+  /**
+   * {summary}{Connection to an instance stored in %Orthanc.}
+   * {description}{This class encapsulates a connection to an image instance
+   * from a remote instance of %Orthanc.}
+   **/
+  class LAAW_API Instance : public Orthanc::IDynamicObject
+  {
+  private:
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value tags_;
+    std::auto_ptr<Orthanc::PngReader> reader_;
+    Orthanc::ImageExtractionMode mode_;
+    std::auto_ptr<std::string> dicom_;
+    std::string content_;
+
+    void DownloadImage();
+
+    void DownloadDicom();
+
+  public:
+     /**
+     * {summary}{Create a connection to some image instance.}
+     * {param}{connection The remote instance of %Orthanc.}
+     * {param}{id The %Orthanc identifier of the image instance.}
+     **/
+    Instance(const OrthancConnection& connection,
+             const char* id);
+
+    
+    /**
+     * {summary}{Get the %Orthanc identifier of this identifier.}
+     * {returns}{The identifier.}
+     **/
+    const char* GetId() const
+    {
+      return id_.c_str();
+    }
+
+
+    /**
+     * {summary}{Set the extraction mode for the 2D image corresponding to this instance.}
+     * {param}{mode The extraction mode.}
+     **/
+    void SetImageExtractionMode(Orthanc::ImageExtractionMode mode);
+
+    /**
+     * {summary}{Get the extraction mode for the 2D image corresponding to this instance.}
+     * {returns}{The extraction mode.}
+     **/
+    Orthanc::ImageExtractionMode GetImageExtractionMode() const
+    {
+      return mode_;
+    }
+
+    
+    /**
+     * {summary}{Get the string value of some DICOM tag of this instance.}
+     * {param}{tag The name of the tag of interest.}
+     * {returns}{The value of the tag.}
+     **/
+    const char* GetTagAsString(const char* tag) const;
+
+    /**
+     * {summary}{Get the floating point value that is stored in some DICOM tag of this instance.}
+     * {param}{tag The name of the tag of interest.}
+     * {returns}{The value of the tag.}
+     **/
+    float GetTagAsFloat(const char* tag) const;
+
+    /**
+     * {summary}{Get the integer value that is stored in some DICOM tag of this instance.}
+     * {param}{tag The name of the tag of interest.}
+     * {returns}{The value of the tag.}
+     **/
+    int32_t GetTagAsInt(const char* tag) const;
+
+    
+    /**
+     * {summary}{Get the width of the 2D image.}
+     * {description}{Get the width of the 2D image that is encoded by this DICOM instance.}
+     * {returns}{The width.}
+     **/
+    uint32_t GetWidth();
+
+    /**
+     * {summary}{Get the height of the 2D image.}
+     * {description}{Get the height of the 2D image that is encoded by this DICOM instance.}
+     * {returns}{The height.}
+     **/
+    uint32_t GetHeight();
+
+    /**
+     * {summary}{Get the number of bytes between two lines of the image (pitch).}
+     * {description}{Get the number of bytes between two lines of the image in the memory buffer returned by GetBuffer(). This value depends on the extraction mode for the image.}
+     * {returns}{The pitch.}
+     **/
+    uint32_t GetPitch();
+
+    /**
+     * {summary}{Get the format of the pixels of the 2D image.}
+     * {description}{Return the memory layout that is used for the 2D image that is encoded by this DICOM instance. This value depends on the extraction mode for the image.}
+     * {returns}{The pixel format.}
+     **/
+    Orthanc::PixelFormat GetPixelFormat();
+
+    /**
+     * {summary}{Access the memory buffer in which the raw pixels of the 2D image are stored.}
+     * {returns}{A pointer to the memory buffer.}
+     **/
+    const void* GetBuffer();
+
+    /**
+     * {summary}{Access the memory buffer in which the raw pixels of some line of the 2D image are stored.}
+     * {param}{y The line of interest.}
+     * {returns}{A pointer to the memory buffer.}
+     **/
+    const void* GetBuffer(uint32_t y);
+
+    /**
+     * {summary}{Get the size of the DICOM file corresponding to this instance.}
+     * {returns}{The file size.}
+     **/
+    const uint64_t GetDicomSize();
+
+    /**
+     * {summary}{Get a pointer to the content of the DICOM file corresponding to this instance.}
+     * {returns}{The DICOM file.}
+     **/
+    const void* GetDicom();
+
+    /**
+     * {summary}{Discard the downloaded 2D image, so as to make room in memory.}
+     **/
+    void DiscardImage();
+
+    /**
+     * {summary}{Discard the downloaded DICOM file, so as to make room in memory.}
+     **/
+    void DiscardDicom();
+
+    LAAW_API_INTERNAL void SplitVectorOfFloats(std::vector<float>& target,
+                                               const char* tag);
+
+    /**
+     * {summary}{Load a raw tag from the DICOM file.}
+     * {param}{path The path to the tag of interest (e.g. "0020-000d").}
+     **/
+    void LoadTagContent(const char* path);
+
+    /**
+     * {summary}{Return the value of the raw tag that was loaded by LoadContent.}
+     * {returns}{The tag value.}
+     **/
+    const char* GetLoadedTagContent() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/OrthancClientException.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Orthanc/Core/OrthancException.h"
+#include <laaw/laaw.h>
+
+namespace OrthancClient
+{
+  class OrthancClientException : public ::Laaw::LaawException
+  {
+  public:
+    OrthancClientException(Orthanc::ErrorCode code) :
+      LaawException(Orthanc::OrthancException::GetDescription(code))
+    { 
+    }
+
+    OrthancClientException(const char* message) : 
+      LaawException(message)
+    {    
+    }
+
+    OrthancClientException(const std::string& message) : 
+      LaawException(message)
+    {    
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/OrthancConnection.cpp	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,114 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancConnection.h"
+
+#include "../Orthanc/Core/Toolbox.h"
+
+namespace OrthancClient
+{
+  void OrthancConnection::ReadPatients()
+  {
+    client_.SetMethod(Orthanc::HttpMethod_Get);
+    client_.SetUrl(orthancUrl_ + "/patients");
+
+    Json::Value v;
+    if (!client_.Apply(content_))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    std::string id = content_[tmp].asString();
+    return new Patient(*this, id.c_str());
+  }
+
+  Patient& OrthancConnection::GetPatient(unsigned int index)
+  {
+    return dynamic_cast<Patient&>(patients_.GetItem(index));
+  }
+
+  OrthancConnection::OrthancConnection(const char* orthancUrl) : 
+    orthancUrl_(orthancUrl), patients_(*this)
+  {
+    ReadPatients();
+  }
+  
+  OrthancConnection::OrthancConnection(const char* orthancUrl,
+                                       const char* username, 
+                                       const char* password) : 
+    orthancUrl_(orthancUrl), patients_(*this)
+  {
+    client_.SetCredentials(username, password);
+    ReadPatients();
+  }
+
+
+  void OrthancConnection::Store(const void* dicom, uint64_t size)
+  {
+    if (size == 0)
+    {
+      return;
+    }
+
+    client_.SetMethod(Orthanc::HttpMethod_Post);
+    client_.SetUrl(orthancUrl_ + "/instances");
+
+    // Copy the DICOM file in the POST body. TODO - Avoid memory copy
+    client_.AccessPostData().resize(static_cast<size_t>(size));
+    memcpy(&client_.AccessPostData()[0], dicom, static_cast<size_t>(size));
+
+    Json::Value v;
+    if (!client_.Apply(v))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+    
+    Reload();
+  }
+
+
+  void  OrthancConnection::StoreFile(const char* filename)
+  {
+    std::string content;
+    Orthanc::Toolbox::ReadFile(content, filename);
+
+    if (content.size() != 0)
+    {
+      Store(&content[0], content.size());
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/OrthancConnection.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,180 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Orthanc/Core/HttpClient.h"
+
+#include "Patient.h"
+
+namespace OrthancClient
+{
+  /**
+   * {summary}{Connection to an instance of %Orthanc.}
+   * {description}{This class encapsulates a connection to a remote instance
+   * of %Orthanc through its REST API.}
+   **/  
+  class LAAW_API OrthancConnection : 
+    public boost::noncopyable,
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    Orthanc::HttpClient client_;
+    std::string orthancUrl_;
+    Orthanc::ArrayFilledByThreads  patients_;
+    Json::Value content_;
+
+    void ReadPatients();
+
+    virtual size_t GetFillerSize()
+    {
+      return content_.size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+  public:
+    /**
+     * {summary}{Create a connection to an instance of %Orthanc.}
+     * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.}
+     **/
+    OrthancConnection(const char* orthancUrl);
+
+    /**
+     * {summary}{Create a connection to an instance of %Orthanc, with authentication.}
+     * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.}
+     * {param}{username The username.}
+     * {param}{password The password.}
+     **/
+    OrthancConnection(const char* orthancUrl,
+                      const char* username, 
+                      const char* password);
+
+    virtual ~OrthancConnection()
+    {
+    }
+
+    /**
+     * {summary}{Returns the number of threads for this connection.}
+     * {description}{Returns the number of simultaneous connections
+     * that are used when downloading information from this instance
+     * of %Orthanc.} 
+     * {returns}{The number of threads.}
+     **/
+    uint32_t GetThreadCount() const
+    {
+      return patients_.GetThreadCount();
+    }
+
+    /**
+     * {summary}{Sets the number of threads for this connection.}
+     * {description}{Sets  the number of simultaneous connections
+     * that are used when downloading information from this instance
+     * of %Orthanc.} 
+     * {param}{threadCount The number of threads.}
+     **/
+    void SetThreadCount(uint32_t threadCount)
+    {
+      patients_.SetThreadCount(threadCount);
+    }
+
+    /**
+     * {summary}{Reload the list of the patients.}
+     * {description}{This method will reload the list of the patients from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
+     **/
+    void Reload()
+    {
+      ReadPatients();
+      patients_.Invalidate();
+    }
+
+    LAAW_API_INTERNAL const Orthanc::HttpClient& GetHttpClient() const
+    {
+      return client_;
+    }
+
+    /**
+     * {summary}{Returns the URL of this instance of %Orthanc.}
+     * {description}{Returns the URL of the remote %Orthanc instance to which this object is connected.}
+     * {returns}{The URL.}
+     **/
+    const char* GetOrthancUrl() const
+    {
+      return orthancUrl_.c_str();
+    }
+
+    /**
+     * {summary}{Returns the number of patients.}
+     * {description}{Returns the number of patients that are stored in the remote instance of %Orthanc.}
+     * {returns}{The number of patients.}
+     **/
+    uint32_t GetPatientCount()
+    {
+      return patients_.GetSize();
+    }
+
+    /**
+     * {summary}{Get some patient.}
+     * {description}{This method will return an object that contains information about some patient. The patients are indexed by a number between 0 (inclusive) and the result of GetPatientCount() (exclusive).}
+     * {param}{index The index of the patient of interest.}
+     * {returns}{The patient.}
+     **/
+    Patient& GetPatient(uint32_t index);
+
+    /**
+     * {summary}{Delete some patient.}
+     * {description}{Delete some patient from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
+     * {param}{index The index of the patient of interest.}
+     * {returns}{The patient.}
+     **/
+    void DeletePatient(uint32_t index)
+    {
+      GetPatient(index).Delete();
+      Reload();
+    }
+
+    /**
+     * {summary}{Send a DICOM file.}
+     * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
+     * {param}{filename Path to the DICOM file}
+     **/
+    void StoreFile(const char* filename);
+
+    /**
+     * {summary}{Send a DICOM file that is contained inside a memory buffer.}
+     * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
+     * {param}{dicom The memory buffer containing the DICOM file.}
+     * {param}{size The size of the DICOM file.}
+     **/    
+    void Store(const void* dicom, uint64_t size);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Patient.cpp	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,92 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Patient.h"
+
+#include "OrthancConnection.h"
+
+namespace OrthancClient
+{
+  void Patient::ReadPatient()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_);
+
+    Json::Value v;
+    if (!client.Apply(patient_))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    std::string id = patient_["Studies"][tmp].asString();
+    return new Study(connection_, id.c_str());
+  }
+
+  Patient::Patient(const OrthancConnection& connection,
+                   const char* id) :
+    connection_(connection),
+    id_(id),
+    studies_(*this)
+  {
+    studies_.SetThreadCount(connection.GetThreadCount());
+    ReadPatient();
+  }
+
+  const char* Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (patient_["MainDicomTags"].isMember(tag))
+    {
+      return patient_["MainDicomTags"][tag].asCString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+  
+  void Patient::Delete()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetMethod(Orthanc::HttpMethod_Delete);
+    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_);
+    
+    std::string s;
+    if (!client.Apply(s))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Patient.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,121 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Study.h"
+
+namespace OrthancClient
+{
+  /**
+   * {summary}{Connection to a patient stored in %Orthanc.}
+   * {description}{This class encapsulates a connection to a patient
+   * from a remote instance of %Orthanc.}
+   **/
+  class LAAW_API Patient : 
+    public Orthanc::IDynamicObject, 
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value patient_;
+    Orthanc::ArrayFilledByThreads  studies_;
+
+    void ReadPatient();
+
+    virtual size_t GetFillerSize()
+    {
+      return patient_["Studies"].size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+  public:
+    /**
+     * {summary}{Create a connection to some patient.}
+     * {param}{connection The remote instance of %Orthanc.}
+     * {param}{id The %Orthanc identifier of the patient.}
+     **/
+    Patient(const OrthancConnection& connection,
+            const char* id);
+
+    /**
+     * {summary}{Reload the studies of this patient.}
+     * {description}{This method will reload the list of the studies of this patient. Pay attention to the fact that the studies that have been previously returned by GetStudy() will be invalidated.}
+     **/
+    void Reload()
+    {
+      studies_.Reload();
+    }
+
+    /**
+     * {summary}{Return the number of studies for this patient.}
+     * {returns}{The number of studies.}
+     **/
+    uint32_t GetStudyCount()
+    {
+      return studies_.GetSize();
+    }
+
+    /**
+     * {summary}{Get some study of this patient.}
+     * {description}{This method will return an object that contains information about some study. The studies are indexed by a number between 0 (inclusive) and the result of GetStudyCount() (exclusive).}
+     * {param}{index The index of the study of interest.}
+     * {returns}{The study.}
+     **/
+    Study& GetStudy(uint32_t index)
+    {
+      return dynamic_cast<Study&>(studies_.GetItem(index));
+    }
+
+    /**
+     * {summary}{Get the %Orthanc identifier of this patient.}
+     * {returns}{The identifier.}
+     **/
+    const char* GetId() const
+    {
+      return id_.c_str();
+    }
+
+    /**
+     * {summary}{Get the value of one of the main DICOM tags for this patient.}
+     * {param}{tag The name of the tag of interest ("PatientName", "PatientID", "PatientSex" or "PatientBirthDate").}
+     * {param}{defaultValue The default value to be returned if this tag does not exist.}
+     * {returns}{The value of the tag.}
+     **/
+    const char* GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+
+    LAAW_API_INTERNAL void Delete();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Series.cpp	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,526 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Series.h"
+
+#include "OrthancConnection.h"
+
+#include <set>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancClient
+{
+  namespace
+  {
+    class SliceLocator
+    {
+    private:
+      float normal_[3];
+
+    public:
+      SliceLocator(Instance& someSlice)
+      {
+        /**
+         * Compute the slice normal from Image Orientation Patient.
+         * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice
+         * http://dicomiseasy.blogspot.be/2013/06/getting-oriented-using-image-plane.html
+         * http://www.itk.org/pipermail/insight-users/2003-September/004762.html
+         **/
+
+        std::vector<float> cosines;
+        someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");  // 0020-0037
+
+        if (cosines.size() != 6)
+        {
+          throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
+        normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
+        normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
+      }
+
+
+      /**
+       * Compute the distance of some slice along the slice normal.
+       **/
+      float ComputeSliceLocation(Instance& instance) const
+      {
+        std::vector<float> ipp;
+        instance.SplitVectorOfFloats(ipp, "ImagePositionPatient");  // 0020-0032
+        if (ipp.size() != 3)
+        {
+          throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        float dist = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+          dist += normal_[i] * ipp[i];
+        }
+
+        return dist;
+      }
+    };
+
+    class ImageDownloadCommand : public Orthanc::ICommand
+    {
+    private:
+      Orthanc::PixelFormat format_;
+      Orthanc::ImageExtractionMode mode_;
+      Instance& instance_;
+      void* target_;
+      size_t lineStride_;
+
+    public:
+      ImageDownloadCommand(Instance& instance, 
+                           Orthanc::PixelFormat format,
+                           Orthanc::ImageExtractionMode mode,
+                           void* target,
+                           size_t lineStride) :
+        format_(format),
+        mode_(mode),
+        instance_(instance),
+        target_(target),
+        lineStride_(lineStride)
+      {
+        instance_.SetImageExtractionMode(mode);
+      }
+
+      virtual bool Execute()
+      {
+        using namespace Orthanc;
+
+        unsigned int width = instance_.GetHeight();
+
+        for (unsigned int y = 0; y < instance_.GetHeight(); y++)
+        {
+          uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_;
+
+          if (instance_.GetPixelFormat() == format_)
+          {
+            memcpy(p, instance_.GetBuffer(y), GetBytesPerPixel(instance_.GetPixelFormat()) * instance_.GetWidth());
+          }
+          else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 &&
+                   format_ == PixelFormat_RGB24)
+          {
+            const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y));
+            for (unsigned int x = 0; x < width; x++, s++, p += 3)
+            {
+              p[0] = *s;
+              p[1] = *s;
+              p[2] = *s;
+            }
+          }
+          else
+          {
+            throw OrthancClientException(ErrorCode_NotImplemented);
+          }
+        }
+
+        // Do not keep the image in memory, as we are loading 3D images
+        instance_.DiscardImage();
+
+        return true;
+      }
+    };
+
+
+    class ProgressToFloatListener : public Orthanc::ThreadedCommandProcessor::IListener
+    {
+    private:
+      float* target_;
+
+    public:
+      ProgressToFloatListener(float* target) : target_(target)
+      {
+      }
+
+      virtual void SignalProgress(unsigned int current,
+                                  unsigned int total)
+      {
+        if (total == 0)
+        {
+          *target_ = 0;
+        }
+        else
+        {
+          *target_ = static_cast<float>(current) / static_cast<float>(total);
+        }
+      }
+
+      virtual void SignalSuccess(unsigned int total)
+      {
+        *target_ = 1;
+      }
+
+      virtual void SignalFailure()
+      {
+        *target_ = 0;
+      }
+
+      virtual void SignalCancel()
+      {
+        *target_ = 0;
+      }
+    };
+
+  }
+
+
+  void Series::Check3DImage()
+  {
+    if (!Is3DImage())
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  bool Series::Is3DImageInternal()
+  {
+    try
+    {
+      if (GetInstanceCount() == 0)
+      {
+        // Empty image, use some default value (should never happen)
+        voxelSizeX_ = 1;
+        voxelSizeY_ = 1;
+        voxelSizeZ_ = 1;
+        sliceThickness_ = 1;
+
+        return true;
+      }
+
+      // Choose a reference slice
+      Instance& reference = GetInstance(0);
+
+      // Check that all the child instances share the same 3D parameters
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        Instance& i2 = GetInstance(i);
+
+        if (std::string(reference.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) ||
+            std::string(reference.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) ||
+            std::string(reference.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) ||
+            std::string(reference.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) ||
+            std::string(reference.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing")))
+        {
+          return false;
+        }              
+      }
+
+
+      // Extract X/Y voxel size and slice thickness
+      std::string s = GetInstance(0).GetTagAsString("PixelSpacing");  // 0028-0030
+      size_t pos = s.find('\\');
+      assert(pos != std::string::npos);
+      std::string sy = s.substr(0, pos);
+      std::string sx = s.substr(pos + 1);
+
+      try
+      {
+        voxelSizeX_ = boost::lexical_cast<float>(sx);
+        voxelSizeY_ = boost::lexical_cast<float>(sy);
+      }
+      catch (boost::bad_lexical_cast)
+      {
+        throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      sliceThickness_ = GetInstance(0).GetTagAsFloat("SliceThickness");  // 0018-0050
+
+
+      // Compute the location of each slice to extract the voxel size along Z
+      voxelSizeZ_ = std::numeric_limits<float>::infinity();
+
+      SliceLocator locator(reference);
+      float referenceSliceLocation = locator.ComputeSliceLocation(reference);
+
+      std::set<float> l;
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        float location = locator.ComputeSliceLocation(GetInstance(i));
+        float distanceToReferenceSlice = fabs(location - referenceSliceLocation);
+
+        l.insert(location);
+
+        if (distanceToReferenceSlice > std::numeric_limits<float>::epsilon() &&
+            distanceToReferenceSlice < voxelSizeZ_)
+        {
+          voxelSizeZ_ = distanceToReferenceSlice;
+        }
+      }
+
+
+      // Make sure that 2 slices do not share the same Z location
+      return l.size() == GetInstanceCount();
+    }
+    catch (OrthancClientException)
+    {
+      return false;
+    }
+  }
+
+  void Series::ReadSeries()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+
+    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/series/" + id_);
+    Json::Value v;
+    if (!client.Apply(series_))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Series::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    std::string id = series_["Instances"][tmp].asString();
+    return new Instance(connection_, id.c_str());
+  }
+
+  Series::Series(const OrthancConnection& connection,
+                 const char* id) :
+    connection_(connection),
+    id_(id),
+    instances_(*this)
+  {
+    ReadSeries();
+    status_ = Status3DImage_NotTested;
+    url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_;
+
+    voxelSizeX_ = 0;
+    voxelSizeY_ = 0;
+    voxelSizeZ_ = 0;
+    sliceThickness_ = 0;
+
+    instances_.SetThreadCount(connection.GetThreadCount());
+  }
+
+
+  bool Series::Is3DImage()
+  {
+    if (status_ == Status3DImage_NotTested)
+    {
+      status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False;
+    }
+
+    return status_ == Status3DImage_True;
+  }
+
+  unsigned int Series::GetInstanceCount()
+  {
+    return instances_.GetSize();
+  }
+
+  Instance& Series::GetInstance(unsigned int index)
+  {
+    return dynamic_cast<Instance&>(instances_.GetItem(index));
+  }
+
+  unsigned int Series::GetWidth()
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+      return 0;
+    else
+      return GetInstance(0).GetTagAsInt("Columns");
+  }
+
+  unsigned int Series::GetHeight()
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+      return 0;
+    else
+      return GetInstance(0).GetTagAsInt("Rows");
+  }
+
+  const char* Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (series_["MainDicomTags"].isMember(tag))
+    {
+      return series_["MainDicomTags"][tag].asCString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  
+  void Series::Load3DImageInternal(void* target,
+                                   Orthanc::PixelFormat format,
+                                   size_t lineStride,
+                                   size_t stackStride,
+                                   Orthanc::ThreadedCommandProcessor::IListener* listener)
+  {
+    using namespace Orthanc;
+
+    // Choose the extraction mode, depending on the format of the
+    // target image.
+
+    uint8_t bytesPerPixel;
+    ImageExtractionMode mode;
+
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+        bytesPerPixel = 3;
+        mode = ImageExtractionMode_Preview;
+        break;
+
+      case PixelFormat_Grayscale8:
+        bytesPerPixel = 1;
+        mode = ImageExtractionMode_UInt8;  // Preview ???
+        break; 
+
+      case PixelFormat_Grayscale16:
+        bytesPerPixel = 2;
+        mode = ImageExtractionMode_UInt16;
+        break;
+
+      case PixelFormat_SignedGrayscale16:
+        bytesPerPixel = 2;
+        mode = ImageExtractionMode_UInt16;
+        format = PixelFormat_Grayscale16;
+        break;
+
+      default:
+        throw OrthancClientException(ErrorCode_NotImplemented);
+    }
+
+
+    // Check that the target image is properly sized
+    unsigned int sx = GetWidth();
+    unsigned int sy = GetHeight();
+
+    if (lineStride < sx * bytesPerPixel ||
+        stackStride < sx * sy * bytesPerPixel)
+    {
+      throw OrthancClientException(ErrorCode_BadRequest);
+    }
+
+    if (sx == 0 || sy == 0 || GetInstanceCount() == 0)
+    {
+      // Empty image, nothing to do
+      if (listener)
+        listener->SignalSuccess(0);
+      return;
+    }
+
+
+    /**
+     * Order the stacks according to their distance along the slice
+     * normal (using the "Image Position Patient" tag). This works
+     * even if the "SliceLocation" tag is absent.
+     **/
+    SliceLocator locator(GetInstance(0));
+
+    typedef std::map<float, Instance*> Instances;
+    Instances instances;
+    for (unsigned int i = 0; i < GetInstanceCount(); i++)
+    {
+      float dist = locator.ComputeSliceLocation(GetInstance(i));
+      instances[dist] = &GetInstance(i);
+    }
+
+    if (instances.size() != GetInstanceCount())
+    {
+      // Several instances have the same Z coordinate
+      throw OrthancClientException(ErrorCode_NotImplemented);
+    }
+
+
+    // Submit the download of each stack as a set of commands
+    ThreadedCommandProcessor processor(connection_.GetThreadCount());
+
+    if (listener != NULL)
+    {
+      processor.SetListener(*listener);
+    }
+
+    uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target);
+    for (Instances::iterator it = instances.begin(); it != instances.end(); ++it)
+    {
+      processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride));
+      stackTarget += stackStride;
+    }
+
+
+    // Wait for all the stacks to be downloaded
+    if (!processor.Join())
+    {
+      throw OrthancClientException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+  float Series::GetVoxelSizeX()
+  {
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
+    return voxelSizeX_;
+  }
+
+  float Series::GetVoxelSizeY()
+  {
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
+    return voxelSizeY_;
+  }
+
+  float Series::GetVoxelSizeZ()
+  {
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
+    return voxelSizeZ_;
+  }
+
+  float Series::GetSliceThickness()
+  {
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
+    return sliceThickness_;
+  }
+
+  void Series::Load3DImage(void* target,
+                           Orthanc::PixelFormat format,
+                           int64_t lineStride,
+                           int64_t stackStride,
+                           float* progress)
+  {
+    ProgressToFloatListener listener(progress);
+    Load3DImageInternal(target, format, static_cast<size_t>(lineStride), 
+                        static_cast<size_t>(stackStride), &listener);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Series.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,239 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Instance.h"
+
+#include "ArrayFilledByThreads.h"
+#include "ThreadedCommandProcessor.h"
+
+namespace OrthancClient
+{
+  /**
+   * {summary}{Connection to a series stored in %Orthanc.}
+   * {description}{This class encapsulates a connection to a series
+   * from a remote instance of %Orthanc.}
+   **/
+  class LAAW_API Series :
+    public Orthanc::IDynamicObject, 
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    enum Status3DImage
+    {
+      Status3DImage_NotTested,
+      Status3DImage_True,
+      Status3DImage_False
+    };
+
+    const OrthancConnection& connection_;
+    std::string id_, url_;
+    Json::Value series_;
+    Orthanc::ArrayFilledByThreads  instances_;
+    Status3DImage status_;
+
+    float voxelSizeX_;
+    float voxelSizeY_;
+    float voxelSizeZ_;
+    float sliceThickness_;
+
+    void Check3DImage();
+
+    bool Is3DImageInternal();
+
+    void ReadSeries();
+
+    virtual size_t GetFillerSize()
+    {
+      return series_["Instances"].size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+    void Load3DImageInternal(void* target,
+                             Orthanc::PixelFormat format,
+                             size_t lineStride,
+                             size_t stackStride,
+                             Orthanc::ThreadedCommandProcessor::IListener* listener);
+
+  public:
+    /**
+     * {summary}{Create a connection to some series.}
+     * {param}{connection The remote instance of %Orthanc.}
+     * {param}{id The %Orthanc identifier of the series.}
+     **/
+    Series(const OrthancConnection& connection,
+           const char* id);
+
+     /**
+     * {summary}{Reload the instances of this series.}
+     * {description}{This method will reload the list of the instances of this series. Pay attention to the fact that the instances that have been previously returned by GetInstance() will be invalidated.}
+     **/
+    void Reload()
+    {
+      instances_.Reload();
+    }
+
+    /**
+     * {summary}{Return the number of instances for this series.}
+     * {returns}{The number of instances.}
+     **/
+    uint32_t GetInstanceCount();
+    
+    /**
+     * {summary}{Get some instance of this series.}
+     * {description}{This method will return an object that contains information about some instance. The instances are indexed by a number between 0 (inclusive) and the result of GetInstanceCount() (exclusive).}
+     * {param}{index The index of the instance of interest.}
+     * {returns}{The instance.}
+     **/
+    Instance& GetInstance(uint32_t index);
+
+    /**
+     * {summary}{Get the %Orthanc identifier of this series.}
+     * {returns}{The identifier.}
+     **/
+    const char* GetId() const
+    {
+      return id_.c_str();
+    }
+
+    /**
+     * {summary}{Returns the URL to this series.}
+     * {returns}{The URL.}
+     **/
+    const char* GetUrl() const
+    {
+      return url_.c_str();
+    }
+
+   
+    /**
+     * {summary}{Get the value of one of the main DICOM tags for this series.}
+     * {param}{tag The name of the tag of interest ("Modality", "Manufacturer", "SeriesDate", "SeriesDescription", "SeriesInstanceUID"...).}
+     * {param}{defaultValue The default value to be returned if this tag does not exist.}
+     * {returns}{The value of the tag.}
+     **/
+    const char* GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+
+    /**
+     * {summary}{Test whether this series encodes a 3D image that can be downloaded from %Orthanc.}
+     * {returns}{"true" if and only if this is a 3D image.}
+     **/
+    bool Is3DImage();
+
+    /**
+     * {summary}{Get the width of the 3D image.}
+     * {description}{Get the width of the 3D image (i.e. along the X-axis). This call is only valid if this series corresponds to a 3D image.}
+     * {returns}{The width.}
+     **/
+    uint32_t GetWidth();
+
+    /**
+     * {summary}{Get the height of the 3D image.}
+     * {description}{Get the height of the 3D image (i.e. along the Y-axis). This call is only valid if this series corresponds to a 3D image.}
+     * {returns}{The height.}
+     **/
+    uint32_t GetHeight();
+
+    /**
+     * {summary}{Get the physical size of a voxel along the X-axis.}
+     * {description}{Get the physical size of a voxel along the X-axis. This call is only valid if this series corresponds to a 3D image.}
+     * {returns}{The voxel size.}
+     **/
+    float GetVoxelSizeX();
+
+    /**
+     * {summary}{Get the physical size of a voxel along the Y-axis.}
+     * {description}{Get the physical size of a voxel along the Y-axis. This call is only valid if this series corresponds to a 3D image.}
+     * {returns}{The voxel size.}
+     **/
+    float GetVoxelSizeY();
+
+    /**
+     * {summary}{Get the physical size of a voxel along the Z-axis.}
+     * {description}{Get the physical size of a voxel along the Z-axis. This call is only valid if this series corresponds to a 3D image.}
+     * {returns}{The voxel size.}
+     **/
+    float GetVoxelSizeZ();
+
+    /**
+     * {summary}{Get the slice thickness.}
+     * {description}{Get the slice thickness. This call is only valid if this series corresponds to a 3D image.}
+     * {returns}{The slice thickness.}
+     **/
+    float GetSliceThickness();
+
+    LAAW_API_INTERNAL void Load3DImage(void* target,
+                                       Orthanc::PixelFormat format,
+                                       int64_t lineStride,
+                                       int64_t stackStride,
+                                       Orthanc::ThreadedCommandProcessor::IListener& listener)
+    {
+      Load3DImageInternal(target, format, static_cast<size_t>(lineStride), 
+                          static_cast<size_t>(stackStride), &listener);
+    }
+
+    /**
+     * {summary}{Load the 3D image into a memory buffer.}
+     * {description}{Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image.}
+     * {param}{target The target memory buffer.}
+     * {param}{format The memory layout of the voxels.}
+     * {param}{lineStride The number of bytes between two lines in the target memory buffer.}
+     * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.}
+     **/
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     int64_t lineStride,
+                     int64_t stackStride)
+    {
+      Load3DImageInternal(target, format, static_cast<size_t>(lineStride),
+                          static_cast<size_t>(stackStride), NULL);
+    }
+
+    /**
+     * {summary}{Load the 3D image into a memory buffer.}
+     * {description}{Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image. This method will also update a progress indicator to monitor the loading of the image.}
+     * {param}{target The target memory buffer.}
+     * {param}{format The memory layout of the voxels.}
+     * {param}{lineStride The number of bytes between two lines in the target memory buffer.}
+     * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.}
+     * {param}{progress A pointer to a floating-point number that is continuously updated by the download threads to reflect the percentage of completion (between 0 and 1). This value can be read from a separate thread.}
+     **/
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     int64_t lineStride,
+                     int64_t stackStride,
+                     float* progress);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Study.cpp	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,79 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Study.h"
+
+#include "OrthancConnection.h"
+
+namespace OrthancClient
+{
+  void Study::ReadStudy()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/studies/" + id_);
+
+    Json::Value v;
+    if (!client.Apply(study_))
+    {
+      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Study::GetFillerItem(size_t index)
+  {
+    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
+    std::string id = study_["Series"][tmp].asString();
+    return new Series(connection_, id.c_str());
+  }
+
+  Study::Study(const OrthancConnection& connection,
+               const char* id) :
+    connection_(connection),
+    id_(id),
+    series_(*this)
+  {
+    series_.SetThreadCount(connection.GetThreadCount());
+    ReadStudy();
+  }
+
+  const char* Study::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (study_["MainDicomTags"].isMember(tag))
+    {
+      return study_["MainDicomTags"][tag].asCString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/Study.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,119 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Series.h"
+
+namespace OrthancClient
+{
+  /**
+   * {summary}{Connection to a study stored in %Orthanc.}
+   * {description}{This class encapsulates a connection to a study
+   * from a remote instance of %Orthanc.}
+   **/
+  class LAAW_API Study : 
+    public Orthanc::IDynamicObject, 
+    private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value study_;
+    Orthanc::ArrayFilledByThreads  series_;
+
+    void ReadStudy();
+
+    virtual size_t GetFillerSize()
+    {
+      return study_["Series"].size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+  public:
+    /**
+     * {summary}{Create a connection to some study.}
+     * {param}{connection The remote instance of %Orthanc.}
+     * {param}{id The %Orthanc identifier of the study.}
+     **/
+    Study(const OrthancConnection& connection,
+          const char* id);
+
+    /**
+     * {summary}{Reload the series of this study.}
+     * {description}{This method will reload the list of the series of this study. Pay attention to the fact that the series that have been previously returned by GetSeries() will be invalidated.}
+     **/
+    void Reload()
+    {
+      series_.Reload();
+    }
+
+    /**
+     * {summary}{Return the number of series for this study.}
+     * {returns}{The number of series.}
+     **/
+    uint32_t GetSeriesCount()
+    {
+      return series_.GetSize();
+    }
+
+    /**
+     * {summary}{Get some series of this study.}
+     * {description}{This method will return an object that contains information about some series. The series are indexed by a number between 0 (inclusive) and the result of GetSeriesCount() (exclusive).}
+     * {param}{index The index of the series of interest.}
+     * {returns}{The series.}
+     **/
+    Series& GetSeries(uint32_t index)
+    {
+      return dynamic_cast<Series&>(series_.GetItem(index));
+    }
+    
+    /**
+     * {summary}{Get the %Orthanc identifier of this study.}
+     * {returns}{The identifier.}
+     **/
+    const char* GetId() const
+    {
+      return id_.c_str();
+    }
+
+    /**
+     * {summary}{Get the value of one of the main DICOM tags for this study.}
+     * {param}{tag The name of the tag of interest ("StudyDate", "StudyDescription", "StudyInstanceUID" or "StudyTime").}
+     * {param}{defaultValue The default value to be returned if this tag does not exist.}
+     * {returns}{The value of the tag.}
+     **/
+    const char* GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/ThreadedCommandProcessor.cpp	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,210 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ThreadedCommandProcessor.h"
+
+#include "../Orthanc/Core/OrthancException.h"
+
+namespace Orthanc
+{
+  static const int32_t TIMEOUT = 10;
+
+
+  void ThreadedCommandProcessor::Processor(ThreadedCommandProcessor* that)
+  {
+    while (!that->done_)
+    {
+      std::auto_ptr<IDynamicObject> command(that->queue_.Dequeue(TIMEOUT));
+
+      if (command.get() != NULL)
+      {
+        bool success = false;
+
+        try
+        {
+          if (that->success_)
+          {
+            // No command has failed so far
+
+            if (that->cancel_)
+            {
+              // The commands have been canceled. Skip the execution
+              // of this command, yet mark it as succeeded.
+              success = true;
+            }
+            else
+            {
+              success = dynamic_cast<ICommand&>(*command).Execute();
+            }
+          }
+          else
+          {
+            // A command has already failed. Skip the execution of this command.
+          }
+        }
+        catch (OrthancException)
+        {
+        }
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+          assert(that->remainingCommands_ > 0);
+          that->remainingCommands_--;
+
+          if (!success)
+          {
+            if (!that->cancel_ && that->listener_ && that->success_)
+            {
+              // This is the first command that fails
+              that->listener_->SignalFailure();
+            }
+
+            that->success_ = false;
+          }
+          else
+          {
+            if (!that->cancel_ && that->listener_)
+            {
+              if (that->remainingCommands_ == 0)
+              {
+                that->listener_->SignalSuccess(that->totalCommands_);
+              }
+              else
+              {
+                that->listener_->SignalProgress(that->totalCommands_ - that->remainingCommands_,
+                                                that->totalCommands_);
+              }
+            }
+          }
+
+          that->processedCommand_.notify_all();
+        }
+      }
+    }
+  }
+
+
+  ThreadedCommandProcessor::ThreadedCommandProcessor(unsigned int numThreads)
+  {
+    if (numThreads < 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    listener_ = NULL;
+    success_ = true;
+    done_ = false;
+    cancel_ = false;
+    threads_.resize(numThreads);
+    remainingCommands_ = 0;
+    totalCommands_ = 0;
+
+    for (unsigned int i = 0; i < numThreads; i++)
+    {
+      threads_[i] = new boost::thread(Processor, this);
+    }
+  }
+
+
+  ThreadedCommandProcessor::~ThreadedCommandProcessor()
+  {
+    done_ = true;
+      
+    for (unsigned int i = 0; i < threads_.size(); i++)
+    {
+      boost::thread* t = threads_[i];
+
+      if (t != NULL)
+      {
+        if (t->joinable())
+        {
+          t->join();
+        }
+
+        delete t;
+      }
+    }
+  }
+
+
+  void ThreadedCommandProcessor::Post(ICommand* command)
+  {
+    if (command == NULL)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    boost::mutex::scoped_lock lock(mutex_);
+    queue_.Enqueue(command);
+    remainingCommands_++;
+    totalCommands_++;
+  }
+
+
+  bool ThreadedCommandProcessor::Join()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (remainingCommands_ != 0)
+    {
+      processedCommand_.wait(lock);
+    }
+
+    if (cancel_ && listener_)
+    {
+      listener_->SignalCancel();
+    }
+
+    // Reset the sequence counters for subsequent commands
+    bool hasSucceeded = success_;
+    success_ = true;
+    totalCommands_ = 0;
+    cancel_ = false;
+
+    return hasSucceeded;
+  }
+
+
+  void ThreadedCommandProcessor::Cancel()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    cancel_ = true;
+  }
+
+
+  void ThreadedCommandProcessor::SetListener(IListener& listener)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    listener_ = &listener;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CppClient/ThreadedCommandProcessor.h	Tue Jun 02 10:11:34 2015 +0200
@@ -0,0 +1,94 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Orthanc/Core/ICommand.h"
+
+#include "../Orthanc/Core/MultiThreading/SharedMessageQueue.h"
+
+namespace OrthancClient
+{
+  class ThreadedCommandProcessor
+  {
+  public:
+    class IListener
+    {
+    public:
+      virtual ~IListener()
+      {
+      }
+
+      virtual void SignalProgress(unsigned int current,
+                                  unsigned int total) = 0;
+
+      virtual void SignalSuccess(unsigned int total) = 0;
+
+      virtual void SignalFailure() = 0;
+
+      virtual void SignalCancel() = 0;
+    };
+
+  private:
+    SharedMessageQueue  queue_;
+    bool done_;
+    bool cancel_;
+    std::vector<boost::thread*>  threads_;
+    IListener* listener_;
+
+    boost::mutex mutex_;
+    bool success_;
+    unsigned int remainingCommands_, totalCommands_;
+    boost::condition_variable processedCommand_;
+
+    static void Processor(ThreadedCommandProcessor* that);
+
+  public:
+    ThreadedCommandProcessor(unsigned int numThreads);
+
+    ~ThreadedCommandProcessor();
+
+    // This takes the ownership of the command
+    void Post(ICommand* command);
+
+    bool Join();
+
+    void Cancel();
+
+    void SetListener(IListener& listener);
+
+    IListener& GetListener() const
+    {
+      return *listener_;
+    }
+  };
+}
--- a/OrthancCppClient/ArrayFilledByThreads.cpp	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ArrayFilledByThreads.h"
-
-#include "ThreadedCommandProcessor.h"
-#include "../Orthanc/Core/OrthancException.h"
-
-namespace Orthanc
-{
-  class ArrayFilledByThreads::Command : public ICommand
-  {
-  private:
-    ArrayFilledByThreads&  that_;
-    size_t  index_;
-
-  public:
-    Command(ArrayFilledByThreads& that,
-            size_t index) :
-      that_(that),
-      index_(index)
-    {
-    }
-
-    virtual bool Execute()
-    {
-      std::auto_ptr<IDynamicObject> obj(that_.filler_.GetFillerItem(index_));
-      if (obj.get() == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        boost::mutex::scoped_lock lock(that_.mutex_);
-        that_.array_[index_] = obj.release();
-        return true;
-      }
-    }
-  };
-
-  void ArrayFilledByThreads::Clear()
-  {
-    for (size_t i = 0; i < array_.size(); i++)
-    {
-      if (array_[i])
-        delete array_[i];
-    }
-
-    array_.clear();
-    filled_ = false;
-  }
-
-  void ArrayFilledByThreads::Update()
-  {
-    if (!filled_)
-    {
-      array_.resize(filler_.GetFillerSize());
-
-      Orthanc::ThreadedCommandProcessor processor(threadCount_);
-      for (size_t i = 0; i < array_.size(); i++)
-      {
-        processor.Post(new Command(*this, i));
-      }
-
-      processor.Join();
-      filled_ = true;
-    }
-  }
-
-
-  ArrayFilledByThreads::ArrayFilledByThreads(IFiller& filler) : filler_(filler)
-  {
-    filled_ = false;
-    threadCount_ = 4;
-  }
-
-
-  ArrayFilledByThreads::~ArrayFilledByThreads()
-  {
-    Clear();
-  }
-
-  
-  void ArrayFilledByThreads::Reload()
-  {
-    Clear();
-    Update();
-  }
-
-
-  void ArrayFilledByThreads::Invalidate()
-  {
-    Clear();
-  }
-
-
-  void ArrayFilledByThreads::SetThreadCount(unsigned int t)
-  {
-    if (t < 1)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    threadCount_ = t;
-  }
-
-
-  size_t ArrayFilledByThreads::GetSize()
-  {
-    Update();
-    return array_.size();
-  }
-
-
-  IDynamicObject& ArrayFilledByThreads::GetItem(size_t index)
-  {
-    if (index >= GetSize())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-
-    return *array_[index];
-  }
-}
--- a/OrthancCppClient/ArrayFilledByThreads.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <boost/thread.hpp>
-
-#include "../Orthanc/Core/IDynamicObject.h"
-
-namespace OrthancClient
-{
-  class ArrayFilledByThreads
-  {
-  public:
-    class IFiller
-    {
-    public:
-      virtual size_t GetFillerSize() = 0;
-
-      virtual IDynamicObject* GetFillerItem(size_t index) = 0;
-    };
-
-  private:
-    IFiller& filler_;
-    boost::mutex  mutex_;
-    std::vector<IDynamicObject*>  array_;
-    bool filled_;
-    unsigned int threadCount_;
-
-    class Command;
-
-    void Clear();
-
-    void Update();
-
-  public:
-    ArrayFilledByThreads(IFiller& filler);
-
-    ~ArrayFilledByThreads();
-  
-    void Reload();
-
-    void Invalidate();
-
-    void SetThreadCount(unsigned int t);
-
-    unsigned int GetThreadCount() const
-    {
-      return threadCount_;
-    }
-
-    size_t GetSize();
-
-    IDynamicObject& GetItem(size_t index);
-  };
-}
-
--- a/OrthancCppClient/Instance.cpp	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Instance.h"
-
-#include "OrthancConnection.h"
-
-#include <boost/lexical_cast.hpp>
-
-namespace OrthancClient
-{
-  void Instance::DownloadImage()
-  {
-    if (reader_.get() == NULL)
-    {
-      const char* suffix;
-      switch (mode_)
-      {
-        case Orthanc::ImageExtractionMode_Preview:
-          suffix = "preview";
-          break;
-          
-        case Orthanc::ImageExtractionMode_UInt8:
-          suffix = "image-uint8";
-          break;
-          
-        case Orthanc::ImageExtractionMode_UInt16:
-          suffix = "image-uint16";
-          break;
-          
-        case Orthanc::ImageExtractionMode_Int16:
-          suffix = "image-int16";
-          break;
-          
-        default:
-          throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
-      }
-
-      Orthanc::HttpClient client(connection_.GetHttpClient());
-      client.SetUrl(std::string(connection_.GetOrthancUrl()) +  "/instances/" + id_ + "/" + suffix);
-      std::string png;
-
-      if (!client.Apply(png))
-      {
-        throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
-      }
-     
-      reader_.reset(new Orthanc::PngReader);
-      reader_->ReadFromMemory(png);
-    }
-  }
-
-  void Instance::DownloadDicom()
-  {
-    if (dicom_.get() == NULL)
-    {
-      Orthanc::HttpClient client(connection_.GetHttpClient());
-      client.SetUrl(std::string(connection_.GetOrthancUrl()) +  "/instances/" + id_ + "/file");
-
-      dicom_.reset(new std::string);
-
-      if (!client.Apply(*dicom_))
-      {
-        throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-      }
-    }
-  }
-
-  Instance::Instance(const OrthancConnection& connection,
-                     const char* id) :
-    connection_(connection),
-    id_(id),
-    mode_(Orthanc::ImageExtractionMode_Int16)
-  {
-    Orthanc::HttpClient client(connection_.GetHttpClient());
-            
-    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/simplified-tags");
-    Json::Value v;
-    if (!client.Apply(tags_))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-  }
-
-  const char* Instance::GetTagAsString(const char* tag) const
-  {
-    if (tags_.isMember(tag))
-    {
-      return tags_[tag].asCString();
-    }
-    else
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_InexistentItem);
-    }
-  }
-
-  float Instance::GetTagAsFloat(const char* tag) const
-  {
-    std::string value = GetTagAsString(tag);
-
-    try
-    {
-      return boost::lexical_cast<float>(value);
-    }
-    catch (boost::bad_lexical_cast)
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-  int Instance::GetTagAsInt(const char* tag) const
-  {
-    std::string value = GetTagAsString(tag);
-
-    try
-    {
-      return boost::lexical_cast<int>(value);
-    }
-    catch (boost::bad_lexical_cast)
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-  unsigned int Instance::GetWidth()
-  {
-    DownloadImage();
-    return reader_->GetWidth();
-  }
-
-  unsigned int Instance::GetHeight() 
-  {
-    DownloadImage();
-    return reader_->GetHeight();
-  }
-
-  unsigned int Instance::GetPitch()
-  {
-    DownloadImage();
-    return reader_->GetPitch();
-  }
-
-  Orthanc::PixelFormat Instance::GetPixelFormat()
-  {
-    DownloadImage();
-    return reader_->GetFormat();
-  }
-
-  const void* Instance::GetBuffer()
-  {
-    DownloadImage();
-    return reader_->GetConstBuffer();
-  }
-
-  const void* Instance::GetBuffer(unsigned int y)
-  {
-    DownloadImage();
-    return reader_->GetConstRow(y);
-  }
-
-  void Instance::DiscardImage()
-  {
-    reader_.reset();
-  }
-
-  void Instance::DiscardDicom()
-  {
-    dicom_.reset();
-  }
-
-
-  void Instance::SetImageExtractionMode(Orthanc::ImageExtractionMode mode)
-  {
-    if (mode_ == mode)
-    {
-      return;
-    }
-
-    DiscardImage();
-    mode_ = mode;
-  }
-
-
-  void Instance::SplitVectorOfFloats(std::vector<float>& target,
-                                     const char* tag)
-  {
-    const std::string value = GetTagAsString(tag);
-
-    target.clear();
-
-    try
-    {
-      std::string tmp;
-      for (size_t i = 0; i < value.size(); i++)
-      {
-        if (value[i] == '\\')
-        {
-          target.push_back(boost::lexical_cast<float>(tmp));
-          tmp.clear();
-        }
-        else
-        {
-          tmp.push_back(value[i]);
-        }
-      }
-
-      target.push_back(boost::lexical_cast<float>(tmp));
-    }
-    catch (boost::bad_lexical_cast)
-    {
-      // Unable to parse the Image Orientation Patient.
-      throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  const uint64_t Instance::GetDicomSize()
-  {
-    DownloadDicom();
-    assert(dicom_.get() != NULL);
-    return dicom_->size();
-  }
-
-  const void* Instance::GetDicom()
-  {
-    DownloadDicom();
-    assert(dicom_.get() != NULL);
-
-    if (dicom_->size() == 0)
-    {
-      return NULL;
-    }
-    else
-    {
-      return &((*dicom_) [0]);
-    }
-  }
-
-
-  void Instance::LoadTagContent(const char* path)
-  {
-    Orthanc::HttpClient client(connection_.GetHttpClient());
-    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/content/" + path);
-
-    if (!client.Apply(content_))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_UnknownResource);
-    }
-  }
-
-
-  const char* Instance::GetLoadedTagContent() const
-  {
-    return content_.c_str();
-  }
-}
--- a/OrthancCppClient/Instance.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <string>
-#include <json/value.h>
-
-#include "OrthancClientException.h"
-#include "../Orthanc/Core/IDynamicObject.h"
-#include "../Orthanc/Core/ImageFormats/PngReader.h"
-
-namespace OrthancClient
-{
-  class OrthancConnection;
-
-  /**
-   * {summary}{Connection to an instance stored in %Orthanc.}
-   * {description}{This class encapsulates a connection to an image instance
-   * from a remote instance of %Orthanc.}
-   **/
-  class LAAW_API Instance : public Orthanc::IDynamicObject
-  {
-  private:
-    const OrthancConnection& connection_;
-    std::string id_;
-    Json::Value tags_;
-    std::auto_ptr<Orthanc::PngReader> reader_;
-    Orthanc::ImageExtractionMode mode_;
-    std::auto_ptr<std::string> dicom_;
-    std::string content_;
-
-    void DownloadImage();
-
-    void DownloadDicom();
-
-  public:
-     /**
-     * {summary}{Create a connection to some image instance.}
-     * {param}{connection The remote instance of %Orthanc.}
-     * {param}{id The %Orthanc identifier of the image instance.}
-     **/
-    Instance(const OrthancConnection& connection,
-             const char* id);
-
-    
-    /**
-     * {summary}{Get the %Orthanc identifier of this identifier.}
-     * {returns}{The identifier.}
-     **/
-    const char* GetId() const
-    {
-      return id_.c_str();
-    }
-
-
-    /**
-     * {summary}{Set the extraction mode for the 2D image corresponding to this instance.}
-     * {param}{mode The extraction mode.}
-     **/
-    void SetImageExtractionMode(Orthanc::ImageExtractionMode mode);
-
-    /**
-     * {summary}{Get the extraction mode for the 2D image corresponding to this instance.}
-     * {returns}{The extraction mode.}
-     **/
-    Orthanc::ImageExtractionMode GetImageExtractionMode() const
-    {
-      return mode_;
-    }
-
-    
-    /**
-     * {summary}{Get the string value of some DICOM tag of this instance.}
-     * {param}{tag The name of the tag of interest.}
-     * {returns}{The value of the tag.}
-     **/
-    const char* GetTagAsString(const char* tag) const;
-
-    /**
-     * {summary}{Get the floating point value that is stored in some DICOM tag of this instance.}
-     * {param}{tag The name of the tag of interest.}
-     * {returns}{The value of the tag.}
-     **/
-    float GetTagAsFloat(const char* tag) const;
-
-    /**
-     * {summary}{Get the integer value that is stored in some DICOM tag of this instance.}
-     * {param}{tag The name of the tag of interest.}
-     * {returns}{The value of the tag.}
-     **/
-    int32_t GetTagAsInt(const char* tag) const;
-
-    
-    /**
-     * {summary}{Get the width of the 2D image.}
-     * {description}{Get the width of the 2D image that is encoded by this DICOM instance.}
-     * {returns}{The width.}
-     **/
-    uint32_t GetWidth();
-
-    /**
-     * {summary}{Get the height of the 2D image.}
-     * {description}{Get the height of the 2D image that is encoded by this DICOM instance.}
-     * {returns}{The height.}
-     **/
-    uint32_t GetHeight();
-
-    /**
-     * {summary}{Get the number of bytes between two lines of the image (pitch).}
-     * {description}{Get the number of bytes between two lines of the image in the memory buffer returned by GetBuffer(). This value depends on the extraction mode for the image.}
-     * {returns}{The pitch.}
-     **/
-    uint32_t GetPitch();
-
-    /**
-     * {summary}{Get the format of the pixels of the 2D image.}
-     * {description}{Return the memory layout that is used for the 2D image that is encoded by this DICOM instance. This value depends on the extraction mode for the image.}
-     * {returns}{The pixel format.}
-     **/
-    Orthanc::PixelFormat GetPixelFormat();
-
-    /**
-     * {summary}{Access the memory buffer in which the raw pixels of the 2D image are stored.}
-     * {returns}{A pointer to the memory buffer.}
-     **/
-    const void* GetBuffer();
-
-    /**
-     * {summary}{Access the memory buffer in which the raw pixels of some line of the 2D image are stored.}
-     * {param}{y The line of interest.}
-     * {returns}{A pointer to the memory buffer.}
-     **/
-    const void* GetBuffer(uint32_t y);
-
-    /**
-     * {summary}{Get the size of the DICOM file corresponding to this instance.}
-     * {returns}{The file size.}
-     **/
-    const uint64_t GetDicomSize();
-
-    /**
-     * {summary}{Get a pointer to the content of the DICOM file corresponding to this instance.}
-     * {returns}{The DICOM file.}
-     **/
-    const void* GetDicom();
-
-    /**
-     * {summary}{Discard the downloaded 2D image, so as to make room in memory.}
-     **/
-    void DiscardImage();
-
-    /**
-     * {summary}{Discard the downloaded DICOM file, so as to make room in memory.}
-     **/
-    void DiscardDicom();
-
-    LAAW_API_INTERNAL void SplitVectorOfFloats(std::vector<float>& target,
-                                               const char* tag);
-
-    /**
-     * {summary}{Load a raw tag from the DICOM file.}
-     * {param}{path The path to the tag of interest (e.g. "0020-000d").}
-     **/
-    void LoadTagContent(const char* path);
-
-    /**
-     * {summary}{Return the value of the raw tag that was loaded by LoadContent.}
-     * {returns}{The tag value.}
-     **/
-    const char* GetLoadedTagContent() const;
-  };
-}
--- a/OrthancCppClient/OrthancClientException.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Orthanc/Core/OrthancException.h"
-#include <laaw/laaw.h>
-
-namespace OrthancClient
-{
-  class OrthancClientException : public ::Laaw::LaawException
-  {
-  public:
-    OrthancClientException(Orthanc::ErrorCode code) :
-      LaawException(Orthanc::OrthancException::GetDescription(code))
-    { 
-    }
-
-    OrthancClientException(const char* message) : 
-      LaawException(message)
-    {    
-    }
-
-    OrthancClientException(const std::string& message) : 
-      LaawException(message)
-    {    
-    }
-  };
-}
--- a/OrthancCppClient/OrthancConnection.cpp	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "OrthancConnection.h"
-
-#include "../Orthanc/Core/Toolbox.h"
-
-namespace OrthancClient
-{
-  void OrthancConnection::ReadPatients()
-  {
-    client_.SetMethod(Orthanc::HttpMethod_Get);
-    client_.SetUrl(orthancUrl_ + "/patients");
-
-    Json::Value v;
-    if (!client_.Apply(content_))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-  }
-
-  Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index)
-  {
-    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
-    std::string id = content_[tmp].asString();
-    return new Patient(*this, id.c_str());
-  }
-
-  Patient& OrthancConnection::GetPatient(unsigned int index)
-  {
-    return dynamic_cast<Patient&>(patients_.GetItem(index));
-  }
-
-  OrthancConnection::OrthancConnection(const char* orthancUrl) : 
-    orthancUrl_(orthancUrl), patients_(*this)
-  {
-    ReadPatients();
-  }
-  
-  OrthancConnection::OrthancConnection(const char* orthancUrl,
-                                       const char* username, 
-                                       const char* password) : 
-    orthancUrl_(orthancUrl), patients_(*this)
-  {
-    client_.SetCredentials(username, password);
-    ReadPatients();
-  }
-
-
-  void OrthancConnection::Store(const void* dicom, uint64_t size)
-  {
-    if (size == 0)
-    {
-      return;
-    }
-
-    client_.SetMethod(Orthanc::HttpMethod_Post);
-    client_.SetUrl(orthancUrl_ + "/instances");
-
-    // Copy the DICOM file in the POST body. TODO - Avoid memory copy
-    client_.AccessPostData().resize(static_cast<size_t>(size));
-    memcpy(&client_.AccessPostData()[0], dicom, static_cast<size_t>(size));
-
-    Json::Value v;
-    if (!client_.Apply(v))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-    
-    Reload();
-  }
-
-
-  void  OrthancConnection::StoreFile(const char* filename)
-  {
-    std::string content;
-    Orthanc::Toolbox::ReadFile(content, filename);
-
-    if (content.size() != 0)
-    {
-      Store(&content[0], content.size());
-    }
-  }
-
-}
--- a/OrthancCppClient/OrthancConnection.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Orthanc/Core/HttpClient.h"
-
-#include "Patient.h"
-
-namespace OrthancClient
-{
-  /**
-   * {summary}{Connection to an instance of %Orthanc.}
-   * {description}{This class encapsulates a connection to a remote instance
-   * of %Orthanc through its REST API.}
-   **/  
-  class LAAW_API OrthancConnection : 
-    public boost::noncopyable,
-    private Orthanc::ArrayFilledByThreads::IFiller
-  {
-  private:
-    Orthanc::HttpClient client_;
-    std::string orthancUrl_;
-    Orthanc::ArrayFilledByThreads  patients_;
-    Json::Value content_;
-
-    void ReadPatients();
-
-    virtual size_t GetFillerSize()
-    {
-      return content_.size();
-    }
-
-    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
-
-  public:
-    /**
-     * {summary}{Create a connection to an instance of %Orthanc.}
-     * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.}
-     **/
-    OrthancConnection(const char* orthancUrl);
-
-    /**
-     * {summary}{Create a connection to an instance of %Orthanc, with authentication.}
-     * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.}
-     * {param}{username The username.}
-     * {param}{password The password.}
-     **/
-    OrthancConnection(const char* orthancUrl,
-                      const char* username, 
-                      const char* password);
-
-    virtual ~OrthancConnection()
-    {
-    }
-
-    /**
-     * {summary}{Returns the number of threads for this connection.}
-     * {description}{Returns the number of simultaneous connections
-     * that are used when downloading information from this instance
-     * of %Orthanc.} 
-     * {returns}{The number of threads.}
-     **/
-    uint32_t GetThreadCount() const
-    {
-      return patients_.GetThreadCount();
-    }
-
-    /**
-     * {summary}{Sets the number of threads for this connection.}
-     * {description}{Sets  the number of simultaneous connections
-     * that are used when downloading information from this instance
-     * of %Orthanc.} 
-     * {param}{threadCount The number of threads.}
-     **/
-    void SetThreadCount(uint32_t threadCount)
-    {
-      patients_.SetThreadCount(threadCount);
-    }
-
-    /**
-     * {summary}{Reload the list of the patients.}
-     * {description}{This method will reload the list of the patients from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
-     **/
-    void Reload()
-    {
-      ReadPatients();
-      patients_.Invalidate();
-    }
-
-    LAAW_API_INTERNAL const Orthanc::HttpClient& GetHttpClient() const
-    {
-      return client_;
-    }
-
-    /**
-     * {summary}{Returns the URL of this instance of %Orthanc.}
-     * {description}{Returns the URL of the remote %Orthanc instance to which this object is connected.}
-     * {returns}{The URL.}
-     **/
-    const char* GetOrthancUrl() const
-    {
-      return orthancUrl_.c_str();
-    }
-
-    /**
-     * {summary}{Returns the number of patients.}
-     * {description}{Returns the number of patients that are stored in the remote instance of %Orthanc.}
-     * {returns}{The number of patients.}
-     **/
-    uint32_t GetPatientCount()
-    {
-      return patients_.GetSize();
-    }
-
-    /**
-     * {summary}{Get some patient.}
-     * {description}{This method will return an object that contains information about some patient. The patients are indexed by a number between 0 (inclusive) and the result of GetPatientCount() (exclusive).}
-     * {param}{index The index of the patient of interest.}
-     * {returns}{The patient.}
-     **/
-    Patient& GetPatient(uint32_t index);
-
-    /**
-     * {summary}{Delete some patient.}
-     * {description}{Delete some patient from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
-     * {param}{index The index of the patient of interest.}
-     * {returns}{The patient.}
-     **/
-    void DeletePatient(uint32_t index)
-    {
-      GetPatient(index).Delete();
-      Reload();
-    }
-
-    /**
-     * {summary}{Send a DICOM file.}
-     * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
-     * {param}{filename Path to the DICOM file}
-     **/
-    void StoreFile(const char* filename);
-
-    /**
-     * {summary}{Send a DICOM file that is contained inside a memory buffer.}
-     * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.}
-     * {param}{dicom The memory buffer containing the DICOM file.}
-     * {param}{size The size of the DICOM file.}
-     **/    
-    void Store(const void* dicom, uint64_t size);
-  };
-}
--- a/OrthancCppClient/Patient.cpp	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Patient.h"
-
-#include "OrthancConnection.h"
-
-namespace OrthancClient
-{
-  void Patient::ReadPatient()
-  {
-    Orthanc::HttpClient client(connection_.GetHttpClient());
-    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_);
-
-    Json::Value v;
-    if (!client.Apply(patient_))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-  }
-
-  Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index)
-  {
-    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
-    std::string id = patient_["Studies"][tmp].asString();
-    return new Study(connection_, id.c_str());
-  }
-
-  Patient::Patient(const OrthancConnection& connection,
-                   const char* id) :
-    connection_(connection),
-    id_(id),
-    studies_(*this)
-  {
-    studies_.SetThreadCount(connection.GetThreadCount());
-    ReadPatient();
-  }
-
-  const char* Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const
-  {
-    if (patient_["MainDicomTags"].isMember(tag))
-    {
-      return patient_["MainDicomTags"][tag].asCString();
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-  
-  void Patient::Delete()
-  {
-    Orthanc::HttpClient client(connection_.GetHttpClient());
-    client.SetMethod(Orthanc::HttpMethod_Delete);
-    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_);
-    
-    std::string s;
-    if (!client.Apply(s))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-  }
-}
--- a/OrthancCppClient/Patient.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Study.h"
-
-namespace OrthancClient
-{
-  /**
-   * {summary}{Connection to a patient stored in %Orthanc.}
-   * {description}{This class encapsulates a connection to a patient
-   * from a remote instance of %Orthanc.}
-   **/
-  class LAAW_API Patient : 
-    public Orthanc::IDynamicObject, 
-    private Orthanc::ArrayFilledByThreads::IFiller
-  {
-  private:
-    const OrthancConnection& connection_;
-    std::string id_;
-    Json::Value patient_;
-    Orthanc::ArrayFilledByThreads  studies_;
-
-    void ReadPatient();
-
-    virtual size_t GetFillerSize()
-    {
-      return patient_["Studies"].size();
-    }
-
-    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
-
-  public:
-    /**
-     * {summary}{Create a connection to some patient.}
-     * {param}{connection The remote instance of %Orthanc.}
-     * {param}{id The %Orthanc identifier of the patient.}
-     **/
-    Patient(const OrthancConnection& connection,
-            const char* id);
-
-    /**
-     * {summary}{Reload the studies of this patient.}
-     * {description}{This method will reload the list of the studies of this patient. Pay attention to the fact that the studies that have been previously returned by GetStudy() will be invalidated.}
-     **/
-    void Reload()
-    {
-      studies_.Reload();
-    }
-
-    /**
-     * {summary}{Return the number of studies for this patient.}
-     * {returns}{The number of studies.}
-     **/
-    uint32_t GetStudyCount()
-    {
-      return studies_.GetSize();
-    }
-
-    /**
-     * {summary}{Get some study of this patient.}
-     * {description}{This method will return an object that contains information about some study. The studies are indexed by a number between 0 (inclusive) and the result of GetStudyCount() (exclusive).}
-     * {param}{index The index of the study of interest.}
-     * {returns}{The study.}
-     **/
-    Study& GetStudy(uint32_t index)
-    {
-      return dynamic_cast<Study&>(studies_.GetItem(index));
-    }
-
-    /**
-     * {summary}{Get the %Orthanc identifier of this patient.}
-     * {returns}{The identifier.}
-     **/
-    const char* GetId() const
-    {
-      return id_.c_str();
-    }
-
-    /**
-     * {summary}{Get the value of one of the main DICOM tags for this patient.}
-     * {param}{tag The name of the tag of interest ("PatientName", "PatientID", "PatientSex" or "PatientBirthDate").}
-     * {param}{defaultValue The default value to be returned if this tag does not exist.}
-     * {returns}{The value of the tag.}
-     **/
-    const char* GetMainDicomTag(const char* tag, 
-                                const char* defaultValue) const;
-
-    LAAW_API_INTERNAL void Delete();
-  };
-}
--- a/OrthancCppClient/Series.cpp	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,526 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Series.h"
-
-#include "OrthancConnection.h"
-
-#include <set>
-#include <boost/lexical_cast.hpp>
-
-namespace OrthancClient
-{
-  namespace
-  {
-    class SliceLocator
-    {
-    private:
-      float normal_[3];
-
-    public:
-      SliceLocator(Instance& someSlice)
-      {
-        /**
-         * Compute the slice normal from Image Orientation Patient.
-         * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice
-         * http://dicomiseasy.blogspot.be/2013/06/getting-oriented-using-image-plane.html
-         * http://www.itk.org/pipermail/insight-users/2003-September/004762.html
-         **/
-
-        std::vector<float> cosines;
-        someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");  // 0020-0037
-
-        if (cosines.size() != 6)
-        {
-          throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
-        normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
-        normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
-      }
-
-
-      /**
-       * Compute the distance of some slice along the slice normal.
-       **/
-      float ComputeSliceLocation(Instance& instance) const
-      {
-        std::vector<float> ipp;
-        instance.SplitVectorOfFloats(ipp, "ImagePositionPatient");  // 0020-0032
-        if (ipp.size() != 3)
-        {
-          throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
-        }
-
-        float dist = 0;
-
-        for (int i = 0; i < 3; i++)
-        {
-          dist += normal_[i] * ipp[i];
-        }
-
-        return dist;
-      }
-    };
-
-    class ImageDownloadCommand : public Orthanc::ICommand
-    {
-    private:
-      Orthanc::PixelFormat format_;
-      Orthanc::ImageExtractionMode mode_;
-      Instance& instance_;
-      void* target_;
-      size_t lineStride_;
-
-    public:
-      ImageDownloadCommand(Instance& instance, 
-                           Orthanc::PixelFormat format,
-                           Orthanc::ImageExtractionMode mode,
-                           void* target,
-                           size_t lineStride) :
-        format_(format),
-        mode_(mode),
-        instance_(instance),
-        target_(target),
-        lineStride_(lineStride)
-      {
-        instance_.SetImageExtractionMode(mode);
-      }
-
-      virtual bool Execute()
-      {
-        using namespace Orthanc;
-
-        unsigned int width = instance_.GetHeight();
-
-        for (unsigned int y = 0; y < instance_.GetHeight(); y++)
-        {
-          uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_;
-
-          if (instance_.GetPixelFormat() == format_)
-          {
-            memcpy(p, instance_.GetBuffer(y), GetBytesPerPixel(instance_.GetPixelFormat()) * instance_.GetWidth());
-          }
-          else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 &&
-                   format_ == PixelFormat_RGB24)
-          {
-            const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y));
-            for (unsigned int x = 0; x < width; x++, s++, p += 3)
-            {
-              p[0] = *s;
-              p[1] = *s;
-              p[2] = *s;
-            }
-          }
-          else
-          {
-            throw OrthancClientException(ErrorCode_NotImplemented);
-          }
-        }
-
-        // Do not keep the image in memory, as we are loading 3D images
-        instance_.DiscardImage();
-
-        return true;
-      }
-    };
-
-
-    class ProgressToFloatListener : public Orthanc::ThreadedCommandProcessor::IListener
-    {
-    private:
-      float* target_;
-
-    public:
-      ProgressToFloatListener(float* target) : target_(target)
-      {
-      }
-
-      virtual void SignalProgress(unsigned int current,
-                                  unsigned int total)
-      {
-        if (total == 0)
-        {
-          *target_ = 0;
-        }
-        else
-        {
-          *target_ = static_cast<float>(current) / static_cast<float>(total);
-        }
-      }
-
-      virtual void SignalSuccess(unsigned int total)
-      {
-        *target_ = 1;
-      }
-
-      virtual void SignalFailure()
-      {
-        *target_ = 0;
-      }
-
-      virtual void SignalCancel()
-      {
-        *target_ = 0;
-      }
-    };
-
-  }
-
-
-  void Series::Check3DImage()
-  {
-    if (!Is3DImage())
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
-    }
-  }
-
-  bool Series::Is3DImageInternal()
-  {
-    try
-    {
-      if (GetInstanceCount() == 0)
-      {
-        // Empty image, use some default value (should never happen)
-        voxelSizeX_ = 1;
-        voxelSizeY_ = 1;
-        voxelSizeZ_ = 1;
-        sliceThickness_ = 1;
-
-        return true;
-      }
-
-      // Choose a reference slice
-      Instance& reference = GetInstance(0);
-
-      // Check that all the child instances share the same 3D parameters
-      for (unsigned int i = 0; i < GetInstanceCount(); i++)
-      {
-        Instance& i2 = GetInstance(i);
-
-        if (std::string(reference.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) ||
-            std::string(reference.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) ||
-            std::string(reference.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) ||
-            std::string(reference.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) ||
-            std::string(reference.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing")))
-        {
-          return false;
-        }              
-      }
-
-
-      // Extract X/Y voxel size and slice thickness
-      std::string s = GetInstance(0).GetTagAsString("PixelSpacing");  // 0028-0030
-      size_t pos = s.find('\\');
-      assert(pos != std::string::npos);
-      std::string sy = s.substr(0, pos);
-      std::string sx = s.substr(pos + 1);
-
-      try
-      {
-        voxelSizeX_ = boost::lexical_cast<float>(sx);
-        voxelSizeY_ = boost::lexical_cast<float>(sy);
-      }
-      catch (boost::bad_lexical_cast)
-      {
-        throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      sliceThickness_ = GetInstance(0).GetTagAsFloat("SliceThickness");  // 0018-0050
-
-
-      // Compute the location of each slice to extract the voxel size along Z
-      voxelSizeZ_ = std::numeric_limits<float>::infinity();
-
-      SliceLocator locator(reference);
-      float referenceSliceLocation = locator.ComputeSliceLocation(reference);
-
-      std::set<float> l;
-      for (unsigned int i = 0; i < GetInstanceCount(); i++)
-      {
-        float location = locator.ComputeSliceLocation(GetInstance(i));
-        float distanceToReferenceSlice = fabs(location - referenceSliceLocation);
-
-        l.insert(location);
-
-        if (distanceToReferenceSlice > std::numeric_limits<float>::epsilon() &&
-            distanceToReferenceSlice < voxelSizeZ_)
-        {
-          voxelSizeZ_ = distanceToReferenceSlice;
-        }
-      }
-
-
-      // Make sure that 2 slices do not share the same Z location
-      return l.size() == GetInstanceCount();
-    }
-    catch (OrthancClientException)
-    {
-      return false;
-    }
-  }
-
-  void Series::ReadSeries()
-  {
-    Orthanc::HttpClient client(connection_.GetHttpClient());
-
-    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/series/" + id_);
-    Json::Value v;
-    if (!client.Apply(series_))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-  }
-
-  Orthanc::IDynamicObject* Series::GetFillerItem(size_t index)
-  {
-    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
-    std::string id = series_["Instances"][tmp].asString();
-    return new Instance(connection_, id.c_str());
-  }
-
-  Series::Series(const OrthancConnection& connection,
-                 const char* id) :
-    connection_(connection),
-    id_(id),
-    instances_(*this)
-  {
-    ReadSeries();
-    status_ = Status3DImage_NotTested;
-    url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_;
-
-    voxelSizeX_ = 0;
-    voxelSizeY_ = 0;
-    voxelSizeZ_ = 0;
-    sliceThickness_ = 0;
-
-    instances_.SetThreadCount(connection.GetThreadCount());
-  }
-
-
-  bool Series::Is3DImage()
-  {
-    if (status_ == Status3DImage_NotTested)
-    {
-      status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False;
-    }
-
-    return status_ == Status3DImage_True;
-  }
-
-  unsigned int Series::GetInstanceCount()
-  {
-    return instances_.GetSize();
-  }
-
-  Instance& Series::GetInstance(unsigned int index)
-  {
-    return dynamic_cast<Instance&>(instances_.GetItem(index));
-  }
-
-  unsigned int Series::GetWidth()
-  {
-    Check3DImage();
-
-    if (GetInstanceCount() == 0)
-      return 0;
-    else
-      return GetInstance(0).GetTagAsInt("Columns");
-  }
-
-  unsigned int Series::GetHeight()
-  {
-    Check3DImage();
-
-    if (GetInstanceCount() == 0)
-      return 0;
-    else
-      return GetInstance(0).GetTagAsInt("Rows");
-  }
-
-  const char* Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
-  {
-    if (series_["MainDicomTags"].isMember(tag))
-    {
-      return series_["MainDicomTags"][tag].asCString();
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  
-  void Series::Load3DImageInternal(void* target,
-                                   Orthanc::PixelFormat format,
-                                   size_t lineStride,
-                                   size_t stackStride,
-                                   Orthanc::ThreadedCommandProcessor::IListener* listener)
-  {
-    using namespace Orthanc;
-
-    // Choose the extraction mode, depending on the format of the
-    // target image.
-
-    uint8_t bytesPerPixel;
-    ImageExtractionMode mode;
-
-    switch (format)
-    {
-      case PixelFormat_RGB24:
-        bytesPerPixel = 3;
-        mode = ImageExtractionMode_Preview;
-        break;
-
-      case PixelFormat_Grayscale8:
-        bytesPerPixel = 1;
-        mode = ImageExtractionMode_UInt8;  // Preview ???
-        break; 
-
-      case PixelFormat_Grayscale16:
-        bytesPerPixel = 2;
-        mode = ImageExtractionMode_UInt16;
-        break;
-
-      case PixelFormat_SignedGrayscale16:
-        bytesPerPixel = 2;
-        mode = ImageExtractionMode_UInt16;
-        format = PixelFormat_Grayscale16;
-        break;
-
-      default:
-        throw OrthancClientException(ErrorCode_NotImplemented);
-    }
-
-
-    // Check that the target image is properly sized
-    unsigned int sx = GetWidth();
-    unsigned int sy = GetHeight();
-
-    if (lineStride < sx * bytesPerPixel ||
-        stackStride < sx * sy * bytesPerPixel)
-    {
-      throw OrthancClientException(ErrorCode_BadRequest);
-    }
-
-    if (sx == 0 || sy == 0 || GetInstanceCount() == 0)
-    {
-      // Empty image, nothing to do
-      if (listener)
-        listener->SignalSuccess(0);
-      return;
-    }
-
-
-    /**
-     * Order the stacks according to their distance along the slice
-     * normal (using the "Image Position Patient" tag). This works
-     * even if the "SliceLocation" tag is absent.
-     **/
-    SliceLocator locator(GetInstance(0));
-
-    typedef std::map<float, Instance*> Instances;
-    Instances instances;
-    for (unsigned int i = 0; i < GetInstanceCount(); i++)
-    {
-      float dist = locator.ComputeSliceLocation(GetInstance(i));
-      instances[dist] = &GetInstance(i);
-    }
-
-    if (instances.size() != GetInstanceCount())
-    {
-      // Several instances have the same Z coordinate
-      throw OrthancClientException(ErrorCode_NotImplemented);
-    }
-
-
-    // Submit the download of each stack as a set of commands
-    ThreadedCommandProcessor processor(connection_.GetThreadCount());
-
-    if (listener != NULL)
-    {
-      processor.SetListener(*listener);
-    }
-
-    uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target);
-    for (Instances::iterator it = instances.begin(); it != instances.end(); ++it)
-    {
-      processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride));
-      stackTarget += stackStride;
-    }
-
-
-    // Wait for all the stacks to be downloaded
-    if (!processor.Join())
-    {
-      throw OrthancClientException(ErrorCode_NetworkProtocol);
-    }
-  }
-
-  float Series::GetVoxelSizeX()
-  {
-    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
-    return voxelSizeX_;
-  }
-
-  float Series::GetVoxelSizeY()
-  {
-    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
-    return voxelSizeY_;
-  }
-
-  float Series::GetVoxelSizeZ()
-  {
-    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
-    return voxelSizeZ_;
-  }
-
-  float Series::GetSliceThickness()
-  {
-    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
-    return sliceThickness_;
-  }
-
-  void Series::Load3DImage(void* target,
-                           Orthanc::PixelFormat format,
-                           int64_t lineStride,
-                           int64_t stackStride,
-                           float* progress)
-  {
-    ProgressToFloatListener listener(progress);
-    Load3DImageInternal(target, format, static_cast<size_t>(lineStride), 
-                        static_cast<size_t>(stackStride), &listener);
-  }
-}
--- a/OrthancCppClient/Series.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,239 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Instance.h"
-
-#include "ArrayFilledByThreads.h"
-#include "ThreadedCommandProcessor.h"
-
-namespace OrthancClient
-{
-  /**
-   * {summary}{Connection to a series stored in %Orthanc.}
-   * {description}{This class encapsulates a connection to a series
-   * from a remote instance of %Orthanc.}
-   **/
-  class LAAW_API Series :
-    public Orthanc::IDynamicObject, 
-    private Orthanc::ArrayFilledByThreads::IFiller
-  {
-  private:
-    enum Status3DImage
-    {
-      Status3DImage_NotTested,
-      Status3DImage_True,
-      Status3DImage_False
-    };
-
-    const OrthancConnection& connection_;
-    std::string id_, url_;
-    Json::Value series_;
-    Orthanc::ArrayFilledByThreads  instances_;
-    Status3DImage status_;
-
-    float voxelSizeX_;
-    float voxelSizeY_;
-    float voxelSizeZ_;
-    float sliceThickness_;
-
-    void Check3DImage();
-
-    bool Is3DImageInternal();
-
-    void ReadSeries();
-
-    virtual size_t GetFillerSize()
-    {
-      return series_["Instances"].size();
-    }
-
-    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
-
-    void Load3DImageInternal(void* target,
-                             Orthanc::PixelFormat format,
-                             size_t lineStride,
-                             size_t stackStride,
-                             Orthanc::ThreadedCommandProcessor::IListener* listener);
-
-  public:
-    /**
-     * {summary}{Create a connection to some series.}
-     * {param}{connection The remote instance of %Orthanc.}
-     * {param}{id The %Orthanc identifier of the series.}
-     **/
-    Series(const OrthancConnection& connection,
-           const char* id);
-
-     /**
-     * {summary}{Reload the instances of this series.}
-     * {description}{This method will reload the list of the instances of this series. Pay attention to the fact that the instances that have been previously returned by GetInstance() will be invalidated.}
-     **/
-    void Reload()
-    {
-      instances_.Reload();
-    }
-
-    /**
-     * {summary}{Return the number of instances for this series.}
-     * {returns}{The number of instances.}
-     **/
-    uint32_t GetInstanceCount();
-    
-    /**
-     * {summary}{Get some instance of this series.}
-     * {description}{This method will return an object that contains information about some instance. The instances are indexed by a number between 0 (inclusive) and the result of GetInstanceCount() (exclusive).}
-     * {param}{index The index of the instance of interest.}
-     * {returns}{The instance.}
-     **/
-    Instance& GetInstance(uint32_t index);
-
-    /**
-     * {summary}{Get the %Orthanc identifier of this series.}
-     * {returns}{The identifier.}
-     **/
-    const char* GetId() const
-    {
-      return id_.c_str();
-    }
-
-    /**
-     * {summary}{Returns the URL to this series.}
-     * {returns}{The URL.}
-     **/
-    const char* GetUrl() const
-    {
-      return url_.c_str();
-    }
-
-   
-    /**
-     * {summary}{Get the value of one of the main DICOM tags for this series.}
-     * {param}{tag The name of the tag of interest ("Modality", "Manufacturer", "SeriesDate", "SeriesDescription", "SeriesInstanceUID"...).}
-     * {param}{defaultValue The default value to be returned if this tag does not exist.}
-     * {returns}{The value of the tag.}
-     **/
-    const char* GetMainDicomTag(const char* tag, 
-                                const char* defaultValue) const;
-
-    /**
-     * {summary}{Test whether this series encodes a 3D image that can be downloaded from %Orthanc.}
-     * {returns}{"true" if and only if this is a 3D image.}
-     **/
-    bool Is3DImage();
-
-    /**
-     * {summary}{Get the width of the 3D image.}
-     * {description}{Get the width of the 3D image (i.e. along the X-axis). This call is only valid if this series corresponds to a 3D image.}
-     * {returns}{The width.}
-     **/
-    uint32_t GetWidth();
-
-    /**
-     * {summary}{Get the height of the 3D image.}
-     * {description}{Get the height of the 3D image (i.e. along the Y-axis). This call is only valid if this series corresponds to a 3D image.}
-     * {returns}{The height.}
-     **/
-    uint32_t GetHeight();
-
-    /**
-     * {summary}{Get the physical size of a voxel along the X-axis.}
-     * {description}{Get the physical size of a voxel along the X-axis. This call is only valid if this series corresponds to a 3D image.}
-     * {returns}{The voxel size.}
-     **/
-    float GetVoxelSizeX();
-
-    /**
-     * {summary}{Get the physical size of a voxel along the Y-axis.}
-     * {description}{Get the physical size of a voxel along the Y-axis. This call is only valid if this series corresponds to a 3D image.}
-     * {returns}{The voxel size.}
-     **/
-    float GetVoxelSizeY();
-
-    /**
-     * {summary}{Get the physical size of a voxel along the Z-axis.}
-     * {description}{Get the physical size of a voxel along the Z-axis. This call is only valid if this series corresponds to a 3D image.}
-     * {returns}{The voxel size.}
-     **/
-    float GetVoxelSizeZ();
-
-    /**
-     * {summary}{Get the slice thickness.}
-     * {description}{Get the slice thickness. This call is only valid if this series corresponds to a 3D image.}
-     * {returns}{The slice thickness.}
-     **/
-    float GetSliceThickness();
-
-    LAAW_API_INTERNAL void Load3DImage(void* target,
-                                       Orthanc::PixelFormat format,
-                                       int64_t lineStride,
-                                       int64_t stackStride,
-                                       Orthanc::ThreadedCommandProcessor::IListener& listener)
-    {
-      Load3DImageInternal(target, format, static_cast<size_t>(lineStride), 
-                          static_cast<size_t>(stackStride), &listener);
-    }
-
-    /**
-     * {summary}{Load the 3D image into a memory buffer.}
-     * {description}{Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image.}
-     * {param}{target The target memory buffer.}
-     * {param}{format The memory layout of the voxels.}
-     * {param}{lineStride The number of bytes between two lines in the target memory buffer.}
-     * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.}
-     **/
-    void Load3DImage(void* target,
-                     Orthanc::PixelFormat format,
-                     int64_t lineStride,
-                     int64_t stackStride)
-    {
-      Load3DImageInternal(target, format, static_cast<size_t>(lineStride),
-                          static_cast<size_t>(stackStride), NULL);
-    }
-
-    /**
-     * {summary}{Load the 3D image into a memory buffer.}
-     * {description}{Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image. This method will also update a progress indicator to monitor the loading of the image.}
-     * {param}{target The target memory buffer.}
-     * {param}{format The memory layout of the voxels.}
-     * {param}{lineStride The number of bytes between two lines in the target memory buffer.}
-     * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.}
-     * {param}{progress A pointer to a floating-point number that is continuously updated by the download threads to reflect the percentage of completion (between 0 and 1). This value can be read from a separate thread.}
-     **/
-    void Load3DImage(void* target,
-                     Orthanc::PixelFormat format,
-                     int64_t lineStride,
-                     int64_t stackStride,
-                     float* progress);
-  };
-}
--- a/OrthancCppClient/Study.cpp	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Study.h"
-
-#include "OrthancConnection.h"
-
-namespace OrthancClient
-{
-  void Study::ReadStudy()
-  {
-    Orthanc::HttpClient client(connection_.GetHttpClient());
-    client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/studies/" + id_);
-
-    Json::Value v;
-    if (!client.Apply(study_))
-    {
-      throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol);
-    }
-  }
-
-  Orthanc::IDynamicObject* Study::GetFillerItem(size_t index)
-  {
-    Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index);
-    std::string id = study_["Series"][tmp].asString();
-    return new Series(connection_, id.c_str());
-  }
-
-  Study::Study(const OrthancConnection& connection,
-               const char* id) :
-    connection_(connection),
-    id_(id),
-    series_(*this)
-  {
-    series_.SetThreadCount(connection.GetThreadCount());
-    ReadStudy();
-  }
-
-  const char* Study::GetMainDicomTag(const char* tag, const char* defaultValue) const
-  {
-    if (study_["MainDicomTags"].isMember(tag))
-    {
-      return study_["MainDicomTags"][tag].asCString();
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-}
--- a/OrthancCppClient/Study.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Series.h"
-
-namespace OrthancClient
-{
-  /**
-   * {summary}{Connection to a study stored in %Orthanc.}
-   * {description}{This class encapsulates a connection to a study
-   * from a remote instance of %Orthanc.}
-   **/
-  class LAAW_API Study : 
-    public Orthanc::IDynamicObject, 
-    private Orthanc::ArrayFilledByThreads::IFiller
-  {
-  private:
-    const OrthancConnection& connection_;
-    std::string id_;
-    Json::Value study_;
-    Orthanc::ArrayFilledByThreads  series_;
-
-    void ReadStudy();
-
-    virtual size_t GetFillerSize()
-    {
-      return study_["Series"].size();
-    }
-
-    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
-
-  public:
-    /**
-     * {summary}{Create a connection to some study.}
-     * {param}{connection The remote instance of %Orthanc.}
-     * {param}{id The %Orthanc identifier of the study.}
-     **/
-    Study(const OrthancConnection& connection,
-          const char* id);
-
-    /**
-     * {summary}{Reload the series of this study.}
-     * {description}{This method will reload the list of the series of this study. Pay attention to the fact that the series that have been previously returned by GetSeries() will be invalidated.}
-     **/
-    void Reload()
-    {
-      series_.Reload();
-    }
-
-    /**
-     * {summary}{Return the number of series for this study.}
-     * {returns}{The number of series.}
-     **/
-    uint32_t GetSeriesCount()
-    {
-      return series_.GetSize();
-    }
-
-    /**
-     * {summary}{Get some series of this study.}
-     * {description}{This method will return an object that contains information about some series. The series are indexed by a number between 0 (inclusive) and the result of GetSeriesCount() (exclusive).}
-     * {param}{index The index of the series of interest.}
-     * {returns}{The series.}
-     **/
-    Series& GetSeries(uint32_t index)
-    {
-      return dynamic_cast<Series&>(series_.GetItem(index));
-    }
-    
-    /**
-     * {summary}{Get the %Orthanc identifier of this study.}
-     * {returns}{The identifier.}
-     **/
-    const char* GetId() const
-    {
-      return id_.c_str();
-    }
-
-    /**
-     * {summary}{Get the value of one of the main DICOM tags for this study.}
-     * {param}{tag The name of the tag of interest ("StudyDate", "StudyDescription", "StudyInstanceUID" or "StudyTime").}
-     * {param}{defaultValue The default value to be returned if this tag does not exist.}
-     * {returns}{The value of the tag.}
-     **/
-    const char* GetMainDicomTag(const char* tag, 
-                                const char* defaultValue) const;
-  };
-}
--- a/OrthancCppClient/ThreadedCommandProcessor.cpp	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ThreadedCommandProcessor.h"
-
-#include "../Orthanc/Core/OrthancException.h"
-
-namespace Orthanc
-{
-  static const int32_t TIMEOUT = 10;
-
-
-  void ThreadedCommandProcessor::Processor(ThreadedCommandProcessor* that)
-  {
-    while (!that->done_)
-    {
-      std::auto_ptr<IDynamicObject> command(that->queue_.Dequeue(TIMEOUT));
-
-      if (command.get() != NULL)
-      {
-        bool success = false;
-
-        try
-        {
-          if (that->success_)
-          {
-            // No command has failed so far
-
-            if (that->cancel_)
-            {
-              // The commands have been canceled. Skip the execution
-              // of this command, yet mark it as succeeded.
-              success = true;
-            }
-            else
-            {
-              success = dynamic_cast<ICommand&>(*command).Execute();
-            }
-          }
-          else
-          {
-            // A command has already failed. Skip the execution of this command.
-          }
-        }
-        catch (OrthancException)
-        {
-        }
-
-        {
-          boost::mutex::scoped_lock lock(that->mutex_);
-          assert(that->remainingCommands_ > 0);
-          that->remainingCommands_--;
-
-          if (!success)
-          {
-            if (!that->cancel_ && that->listener_ && that->success_)
-            {
-              // This is the first command that fails
-              that->listener_->SignalFailure();
-            }
-
-            that->success_ = false;
-          }
-          else
-          {
-            if (!that->cancel_ && that->listener_)
-            {
-              if (that->remainingCommands_ == 0)
-              {
-                that->listener_->SignalSuccess(that->totalCommands_);
-              }
-              else
-              {
-                that->listener_->SignalProgress(that->totalCommands_ - that->remainingCommands_,
-                                                that->totalCommands_);
-              }
-            }
-          }
-
-          that->processedCommand_.notify_all();
-        }
-      }
-    }
-  }
-
-
-  ThreadedCommandProcessor::ThreadedCommandProcessor(unsigned int numThreads)
-  {
-    if (numThreads < 1)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    listener_ = NULL;
-    success_ = true;
-    done_ = false;
-    cancel_ = false;
-    threads_.resize(numThreads);
-    remainingCommands_ = 0;
-    totalCommands_ = 0;
-
-    for (unsigned int i = 0; i < numThreads; i++)
-    {
-      threads_[i] = new boost::thread(Processor, this);
-    }
-  }
-
-
-  ThreadedCommandProcessor::~ThreadedCommandProcessor()
-  {
-    done_ = true;
-      
-    for (unsigned int i = 0; i < threads_.size(); i++)
-    {
-      boost::thread* t = threads_[i];
-
-      if (t != NULL)
-      {
-        if (t->joinable())
-        {
-          t->join();
-        }
-
-        delete t;
-      }
-    }
-  }
-
-
-  void ThreadedCommandProcessor::Post(ICommand* command)
-  {
-    if (command == NULL)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    boost::mutex::scoped_lock lock(mutex_);
-    queue_.Enqueue(command);
-    remainingCommands_++;
-    totalCommands_++;
-  }
-
-
-  bool ThreadedCommandProcessor::Join()
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    while (remainingCommands_ != 0)
-    {
-      processedCommand_.wait(lock);
-    }
-
-    if (cancel_ && listener_)
-    {
-      listener_->SignalCancel();
-    }
-
-    // Reset the sequence counters for subsequent commands
-    bool hasSucceeded = success_;
-    success_ = true;
-    totalCommands_ = 0;
-    cancel_ = false;
-
-    return hasSucceeded;
-  }
-
-
-  void ThreadedCommandProcessor::Cancel()
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    cancel_ = true;
-  }
-
-
-  void ThreadedCommandProcessor::SetListener(IListener& listener)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-    listener_ = &listener;
-  }
-}
--- a/OrthancCppClient/ThreadedCommandProcessor.h	Tue Jun 02 10:11:23 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../Orthanc/Core/ICommand.h"
-
-#include "../Orthanc/Core/MultiThreading/SharedMessageQueue.h"
-
-namespace OrthancClient
-{
-  class ThreadedCommandProcessor
-  {
-  public:
-    class IListener
-    {
-    public:
-      virtual ~IListener()
-      {
-      }
-
-      virtual void SignalProgress(unsigned int current,
-                                  unsigned int total) = 0;
-
-      virtual void SignalSuccess(unsigned int total) = 0;
-
-      virtual void SignalFailure() = 0;
-
-      virtual void SignalCancel() = 0;
-    };
-
-  private:
-    SharedMessageQueue  queue_;
-    bool done_;
-    bool cancel_;
-    std::vector<boost::thread*>  threads_;
-    IListener* listener_;
-
-    boost::mutex mutex_;
-    bool success_;
-    unsigned int remainingCommands_, totalCommands_;
-    boost::condition_variable processedCommand_;
-
-    static void Processor(ThreadedCommandProcessor* that);
-
-  public:
-    ThreadedCommandProcessor(unsigned int numThreads);
-
-    ~ThreadedCommandProcessor();
-
-    // This takes the ownership of the command
-    void Post(ICommand* command);
-
-    bool Join();
-
-    void Cancel();
-
-    void SetListener(IListener& listener);
-
-    IListener& GetListener() const
-    {
-      return *listener_;
-    }
-  };
-}