changeset 479:0cd977e94479

initial commit of the c++ client
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 16 Jul 2013 09:08:09 +0200
parents 888f8a778e70
children 482cde3f3c14
files CMakeLists.txt Core/MultiThreading/ArrayFilledByThreads.cpp Core/MultiThreading/ArrayFilledByThreads.h OrthancCppClient/Instance.cpp OrthancCppClient/Instance.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 README
diffstat 14 files changed, 1545 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Jul 16 09:00:25 2013 +0200
+++ b/CMakeLists.txt	Tue Jul 16 09:08:09 2013 +0200
@@ -141,6 +141,7 @@
   Core/MultiThreading/BagOfRunnablesBySteps.cpp
   Core/MultiThreading/SharedMessageQueue.cpp
   Core/MultiThreading/ThreadedCommandProcessor.cpp
+  Core/MultiThreading/ArrayFilledByThreads.cpp
   Core/FileFormats/PngReader.cpp
   Core/FileFormats/PngWriter.cpp
   Core/SQLite/Connection.cpp
@@ -153,6 +154,12 @@
   Core/Uuid.cpp
   Core/Lua/LuaContext.cpp
   Core/Lua/LuaFunctionCall.cpp
+
+  OrthancCppClient/OrthancConnection.cpp
+  OrthancCppClient/Study.cpp
+  OrthancCppClient/Series.cpp
+  OrthancCppClient/Instance.cpp
+  OrthancCppClient/Patient.cpp
   )  
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/ArrayFilledByThreads.cpp	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,121 @@
+#include "ArrayFilledByThreads.h"
+
+#include "../MultiThreading/ThreadedCommandProcessor.h"
+#include "../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/Core/MultiThreading/ArrayFilledByThreads.h	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <boost/thread.hpp>
+
+#include "../ICommand.h"
+
+namespace Orthanc
+{
+  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/OrthancCppClient/Instance.cpp	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,224 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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 "../Core/OrthancException.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 Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      Orthanc::HttpClient client(connection_.GetHttpClient());
+      client.SetUrl(connection_.GetOrthancUrl() +  "/instances/" + id_ + "/" + suffix);
+      std::string png;
+
+      if (!client.Apply(png))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+     
+      reader_.reset(new Orthanc::PngReader);
+      reader_->ReadFromMemory(png);
+    }
+  }
+
+  Instance::Instance(const OrthancConnection& connection,
+                     const std::string& id) :
+    connection_(connection),
+    id_(id),
+    mode_(Orthanc::ImageExtractionMode_Int16)
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+            
+    client.SetUrl(connection_.GetOrthancUrl() + "/instances/" + id_ + "/simplified-tags");
+    Json::Value v;
+    if (!client.Apply(tags_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  std::string Instance::GetTagAsString(const char* tag)
+  {
+    if (tags_.isMember(tag))
+    {
+      return tags_[tag].asString();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+  }
+
+  float Instance::GetTagAsFloat(const char* tag)
+  {
+    std::string value = GetTagAsString(tag);
+
+    try
+    {
+      return boost::lexical_cast<float>(value);
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+
+  int Instance::GetTagAsInt(const char* tag)
+  {
+    std::string value = GetTagAsString(tag);
+
+    try
+    {
+      return boost::lexical_cast<int>(value);
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      throw Orthanc::OrthancException(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_->GetBuffer();
+  }
+
+  const void* Instance::GetBuffer(unsigned int y)
+  {
+    DownloadImage();
+    return reader_->GetBuffer(y);
+  }
+
+  void Instance::DiscardImage()
+  {
+    reader_.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 Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Instance.h	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,90 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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 "../Core/IDynamicObject.h"
+#include "../Core/FileFormats/PngReader.h"
+
+namespace OrthancClient
+{
+  class OrthancConnection;
+
+  class Instance : public Orthanc::IDynamicObject
+  {
+  private:
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value tags_;
+    std::auto_ptr<Orthanc::PngReader> reader_;
+    Orthanc::ImageExtractionMode mode_;
+
+    void DownloadImage();
+
+  public:
+    Instance(const OrthancConnection& connection,
+             const std::string& id);
+
+    void SetImageExtractionMode(Orthanc::ImageExtractionMode mode);
+
+    Orthanc::ImageExtractionMode GetImageExtractionMode() const
+    {
+      return mode_;
+    }
+
+    std::string GetTagAsString(const char* tag);
+
+    float GetTagAsFloat(const char* tag);
+
+    int GetTagAsInt(const char* tag);
+
+    unsigned int GetWidth();
+
+    unsigned int GetHeight();
+
+    unsigned int GetPitch();
+
+    Orthanc::PixelFormat GetPixelFormat();
+
+    const void* GetBuffer();
+
+    const void* GetBuffer(unsigned int y);
+
+    void DiscardImage();
+
+    void SplitVectorOfFloats(std::vector<float>& target,
+                             const char* tag);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/OrthancConnection.cpp	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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 "../Core/OrthancException.h"
+
+namespace OrthancClient
+{
+  void OrthancConnection::ReadPatients()
+  {
+    client_.SetUrl(orthancUrl_ + "/patients");
+    Json::Value v;
+    if (!client_.Apply(content_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index)
+  {
+    return new Patient(*this, content_[index].asString());
+  }
+
+  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();
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/OrthancConnection.h	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,97 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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 "../Core/HttpClient.h"
+
+#include "Patient.h"
+
+namespace OrthancClient
+{
+  class 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:
+    OrthancConnection(const char* orthancUrl);
+
+    OrthancConnection(const char* orthancUrl,
+                      const char* username, 
+                      const char* password);
+
+    unsigned int GetThreadCount() const
+    {
+      return patients_.GetThreadCount();
+    }
+
+    void SetThreadCount(unsigned int threadCount)
+    {
+      patients_.SetThreadCount(threadCount);
+    }
+
+    void Reload()
+    {
+      patients_.Reload();
+    }
+
+    const Orthanc::HttpClient& GetHttpClient() const
+    {
+      return client_;
+    }
+
+    const std::string& GetOrthancUrl() const
+    {
+      return orthancUrl_;
+    }
+
+    unsigned int GetPatientCount()
+    {
+      return patients_.GetSize();
+    }
+
+    Patient& GetPatient(unsigned int index);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Patient.cpp	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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"
+#include "../Core/OrthancException.h"
+
+namespace OrthancClient
+{
+  void Patient::ReadPatient()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetUrl(connection_.GetOrthancUrl() + "/patients/" + id_);
+    Json::Value v;
+    if (!client.Apply(patient_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index)
+  {
+    return new Study(connection_, patient_["Studies"][index].asString());
+  }
+
+  Patient::Patient(const OrthancConnection& connection,
+                   const std::string& id) :
+    connection_(connection),
+    id_(id),
+    studies_(*this)
+  {
+    studies_.SetThreadCount(connection.GetThreadCount());
+    ReadPatient();
+  }
+
+  std::string Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (patient_["MainDicomTags"].isMember(tag))
+    {
+      return patient_["MainDicomTags"][tag].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Patient.h	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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
+{
+  class 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:
+    Patient(const OrthancConnection& connection,
+            const std::string& id);
+
+    void Reload()
+    {
+      studies_.Reload();
+    }
+
+    unsigned int GetStudyCount()
+    {
+      return studies_.GetSize();
+    }
+
+    Study& GetStudy(unsigned int index)
+    {
+      return dynamic_cast<Study&>(studies_.GetItem(index));
+    }
+
+    const std::string& GetId() const
+    {
+      return id_;
+    }
+
+    std::string GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Series.cpp	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,431 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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 "../Core/OrthancException.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://www.itk.org/pipermail/insight-users/2003-September/004762.html
+         **/
+
+        std::vector<float> cosines;
+        someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");
+
+        if (cosines.size() != 6)
+        {
+          throw Orthanc::OrthancException(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");
+        if (ipp.size() != 3)
+        {
+          throw Orthanc::OrthancException(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), 2 * 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 OrthancException(ErrorCode_NotImplemented);
+          }
+        }
+
+        // Do not keep the image in memory, as we are loading 3D images
+        instance_.DiscardImage();
+
+        return true;
+      }
+    };
+  }
+
+
+  void Series::Check3DImage()
+  {
+    if (!Is3DImage())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  bool Series::Is3DImageInternal()
+  {
+    try
+    {
+      if (GetInstanceCount() == 0)
+      {
+        return true;
+      }
+
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        if (GetInstance(0).GetTagAsString("Columns") != GetInstance(i).GetTagAsString("Columns") ||
+            GetInstance(0).GetTagAsString("Rows") != GetInstance(i).GetTagAsString("Rows") ||
+            GetInstance(0).GetTagAsString("ImageOrientationPatient") != GetInstance(i).GetTagAsString("ImageOrientationPatient") ||
+            GetInstance(0).GetTagAsString("SliceThickness") != GetInstance(i).GetTagAsString("SliceThickness") ||
+            GetInstance(0).GetTagAsString("PixelSpacing") != GetInstance(i).GetTagAsString("PixelSpacing"))
+        {
+          return false;
+        }              
+      }
+
+      SliceLocator locator(GetInstance(0));
+      std::set<float> l;
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        l.insert(locator.ComputeSliceLocation(GetInstance(i)));
+      }
+
+      return l.size() == GetInstanceCount();
+    }
+    catch (Orthanc::OrthancException)
+    {
+      return false;
+    }
+  }
+
+  void Series::ReadSeries()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+
+    client.SetUrl(connection_.GetOrthancUrl() + "/series/" + id_);
+    Json::Value v;
+    if (!client.Apply(series_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Series::GetFillerItem(size_t index)
+  {
+    return new Instance(connection_, series_["Instances"][index].asString());
+  }
+
+  Series::Series(const OrthancConnection& connection,
+                 const std::string& id) :
+    connection_(connection),
+    id_(id),
+    instances_(*this)
+  {
+    ReadSeries();
+    status_ = Status3DImage_NotTested;
+
+    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));
+  }
+
+  std::string Series::GetUrl() const
+  {
+    return connection_.GetOrthancUrl() + "/series/" + id_;
+  }
+
+  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");
+  }
+
+  void Series::GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ)
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+    {
+      sizeX = 0;
+      sizeY = 0;
+      sizeZ = 0;
+    }
+    else
+    {
+      try
+      {
+        std::string s = GetInstance(0).GetTagAsString("PixelSpacing");
+        size_t pos = s.find('\\');
+        assert(pos != std::string::npos);
+        std::string sy = s.substr(0, pos);
+        std::string sx = s.substr(pos + 1);
+
+        sizeX = boost::lexical_cast<float>(sx);
+        sizeY = boost::lexical_cast<float>(sy);
+        sizeZ = GetInstance(0).GetTagAsFloat("SliceThickness");
+      }
+      catch (boost::bad_lexical_cast)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+
+
+  std::string Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (series_["MainDicomTags"].isMember(tag))
+    {
+      return series_["MainDicomTags"][tag].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  
+  void Series::Load3DImage(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 OrthancException(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 OrthancException(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 OrthancException(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 OrthancException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Series.h	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,125 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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 "../Core/MultiThreading/ArrayFilledByThreads.h"
+#include "../Core/MultiThreading/ThreadedCommandProcessor.h"
+
+namespace OrthancClient
+{
+  class Series : public Orthanc::IDynamicObject, private Orthanc::ArrayFilledByThreads::IFiller
+  {
+  private:
+    enum Status3DImage
+    {
+      Status3DImage_NotTested,
+      Status3DImage_True,
+      Status3DImage_False
+    };
+
+    const OrthancConnection& connection_;
+    std::string id_;
+    Json::Value series_;
+    Orthanc::ArrayFilledByThreads  instances_;
+    Status3DImage status_;
+  
+    void Check3DImage();
+
+    bool Is3DImageInternal();
+
+    void ReadSeries();
+
+    virtual size_t GetFillerSize()
+    {
+      return series_["Instances"].size();
+    }
+
+    virtual Orthanc::IDynamicObject* GetFillerItem(size_t index);
+
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     size_t lineStride,
+                     size_t stackStride,
+                     Orthanc::ThreadedCommandProcessor::IListener* listener);
+
+  public:
+    Series(const OrthancConnection& connection,
+           const std::string& id);
+
+    void Reload()
+    {
+      instances_.Reload();
+    }
+
+    bool Is3DImage();
+
+    unsigned int GetInstanceCount();
+
+    Instance& GetInstance(unsigned int index);
+
+    const std::string& GetId() const
+    {
+      return id_;
+    }
+
+    std::string GetUrl() const;
+
+    unsigned int GetWidth();
+
+    unsigned int GetHeight();
+
+    void GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ);  
+
+    std::string GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     size_t lineStride,
+                     size_t stackStride,
+                     Orthanc::ThreadedCommandProcessor::IListener& listener)
+    {
+      Load3DImage(target, format, lineStride, stackStride, &listener);
+    }
+
+    void Load3DImage(void* target,
+                     Orthanc::PixelFormat format,
+                     size_t lineStride,
+                     size_t stackStride)
+    {
+      Load3DImage(target, format, lineStride, stackStride, NULL);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Study.cpp	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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"
+#include "../Core/OrthancException.h"
+
+namespace OrthancClient
+{
+  void Study::ReadStudy()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+    client.SetUrl(connection_.GetOrthancUrl() + "/studies/" + id_);
+    Json::Value v;
+    if (!client.Apply(study_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Study::GetFillerItem(size_t index)
+  {
+    return new Series(connection_, study_["Series"][index].asString());
+  }
+
+  Study::Study(const OrthancConnection& connection,
+               const std::string& id) :
+    connection_(connection),
+    id_(id),
+    series_(*this)
+  {
+    series_.SetThreadCount(connection.GetThreadCount());
+    ReadStudy();
+  }
+
+  std::string Study::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (study_["MainDicomTags"].isMember(tag))
+    {
+      return study_["MainDicomTags"][tag].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Study.h	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU 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
+{
+  class 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:
+    Study(const OrthancConnection& connection,
+          const std::string& id);
+
+    void Reload()
+    {
+      series_.Reload();
+    }
+
+    unsigned int GetSeriesCount()
+    {
+      return series_.GetSize();
+    }
+
+    Series& GetSeries(unsigned int index)
+    {
+      return dynamic_cast<Series&>(series_.GetItem(index));
+    }
+
+    const std::string& GetId() const
+    {
+      return id_;
+    }
+
+    std::string GetMainDicomTag(const char* tag, 
+                                const char* defaultValue) const;
+  };
+}
--- a/README	Tue Jul 16 09:00:25 2013 +0200
+++ b/README	Tue Jul 16 09:08:09 2013 +0200
@@ -60,10 +60,6 @@
 
 The following directories have separate licensing terms:
 
-* The files of the "OrthancCppClient/" directory are licensed under
-  the MIT license, which allows commercial products to statically link
-  against the C++ client library.
-
 * The file of the "Core/SQLite/" directory are licensed under the
   3-clause BSD license, as they are derived from the Chromium project.
 
@@ -74,7 +70,7 @@
 This archive contains the following directories:
 
 * Core/               - The core C++ classes (independent of DCMTK)
-* OrthancCppClient/   - Code of the C++ client (under MIT license)
+* OrthancCppClient/   - Code of the C++ client
 * OrthancExplorer/    - Code of the Web application (HTML5/Javascript)
 * OrthancServer/      - Code of the Orthanc server (depends on DCMTK)
 * Resources/          - Scripts, resources for building third-party code