Mercurial > hg > orthanc-client
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_; - } - }; -}