# HG changeset patch # User Sebastien Jodogne # Date 1373958489 -7200 # Node ID 0cd977e94479a124a345d0e753653e369179e86c # Parent 888f8a778e70d7401571ea93a4e444c7fbd60341 initial commit of the c++ client diff -r 888f8a778e70 -r 0cd977e94479 CMakeLists.txt --- a/CMakeLists.txt Tue Jul 16 09:00:25 2013 +0200 +++ b/CMakeLists.txt Tue Jul 16 09:08:09 2013 +0200 @@ -141,6 +141,7 @@ Core/MultiThreading/BagOfRunnablesBySteps.cpp Core/MultiThreading/SharedMessageQueue.cpp Core/MultiThreading/ThreadedCommandProcessor.cpp + Core/MultiThreading/ArrayFilledByThreads.cpp Core/FileFormats/PngReader.cpp Core/FileFormats/PngWriter.cpp Core/SQLite/Connection.cpp @@ -153,6 +154,12 @@ Core/Uuid.cpp Core/Lua/LuaContext.cpp Core/Lua/LuaFunctionCall.cpp + + OrthancCppClient/OrthancConnection.cpp + OrthancCppClient/Study.cpp + OrthancCppClient/Series.cpp + OrthancCppClient/Instance.cpp + OrthancCppClient/Patient.cpp ) diff -r 888f8a778e70 -r 0cd977e94479 Core/MultiThreading/ArrayFilledByThreads.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ArrayFilledByThreads.cpp Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,121 @@ +#include "ArrayFilledByThreads.h" + +#include "../MultiThreading/ThreadedCommandProcessor.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + class ArrayFilledByThreads::Command : public ICommand + { + private: + ArrayFilledByThreads& that_; + size_t index_; + + public: + Command(ArrayFilledByThreads& that, + size_t index) : + that_(that), + index_(index) + { + } + + virtual bool Execute() + { + std::auto_ptr 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]; + } +} diff -r 888f8a778e70 -r 0cd977e94479 Core/MultiThreading/ArrayFilledByThreads.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ArrayFilledByThreads.h Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "../ICommand.h" + +namespace Orthanc +{ + class ArrayFilledByThreads + { + public: + class IFiller + { + public: + virtual size_t GetFillerSize() = 0; + + virtual IDynamicObject* GetFillerItem(size_t index) = 0; + }; + + private: + IFiller& filler_; + boost::mutex mutex_; + std::vector 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); + }; +} + diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Instance.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Instance.cpp Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,224 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "Instance.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +#include + +namespace OrthancClient +{ + void Instance::DownloadImage() + { + if (reader_.get() == NULL) + { + const char* suffix; + switch (mode_) + { + case Orthanc::ImageExtractionMode_Preview: + suffix = "preview"; + break; + + case Orthanc::ImageExtractionMode_UInt8: + suffix = "image-uint8"; + break; + + case Orthanc::ImageExtractionMode_UInt16: + suffix = "image-uint16"; + break; + + case Orthanc::ImageExtractionMode_Int16: + suffix = "image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(connection_.GetOrthancUrl() + "/instances/" + id_ + "/" + suffix); + std::string png; + + if (!client.Apply(png)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + reader_.reset(new Orthanc::PngReader); + reader_->ReadFromMemory(png); + } + } + + Instance::Instance(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + mode_(Orthanc::ImageExtractionMode_Int16) + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + + client.SetUrl(connection_.GetOrthancUrl() + "/instances/" + id_ + "/simplified-tags"); + Json::Value v; + if (!client.Apply(tags_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + std::string Instance::GetTagAsString(const char* tag) + { + if (tags_.isMember(tag)) + { + return tags_[tag].asString(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); + } + } + + float Instance::GetTagAsFloat(const char* tag) + { + std::string value = GetTagAsString(tag); + + try + { + return boost::lexical_cast(value); + } + catch (boost::bad_lexical_cast) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + int Instance::GetTagAsInt(const char* tag) + { + std::string value = GetTagAsString(tag); + + try + { + return boost::lexical_cast(value); + } + catch (boost::bad_lexical_cast) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + unsigned int Instance::GetWidth() + { + DownloadImage(); + return reader_->GetWidth(); + } + + unsigned int Instance::GetHeight() + { + DownloadImage(); + return reader_->GetHeight(); + } + + unsigned int Instance::GetPitch() + { + DownloadImage(); + return reader_->GetPitch(); + } + + Orthanc::PixelFormat Instance::GetPixelFormat() + { + DownloadImage(); + return reader_->GetFormat(); + } + + const void* Instance::GetBuffer() + { + DownloadImage(); + return reader_->GetBuffer(); + } + + const void* Instance::GetBuffer(unsigned int y) + { + DownloadImage(); + return reader_->GetBuffer(y); + } + + void Instance::DiscardImage() + { + reader_.reset(); + } + + + void Instance::SetImageExtractionMode(Orthanc::ImageExtractionMode mode) + { + if (mode_ == mode) + { + return; + } + + DiscardImage(); + mode_ = mode; + } + + + void Instance::SplitVectorOfFloats(std::vector& 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(tmp)); + tmp.clear(); + } + else + { + tmp.push_back(value[i]); + } + } + + target.push_back(boost::lexical_cast(tmp)); + } + catch (boost::bad_lexical_cast) + { + // Unable to parse the Image Orientation Patient. + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Instance.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Instance.h Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,90 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +#include "../Core/IDynamicObject.h" +#include "../Core/FileFormats/PngReader.h" + +namespace OrthancClient +{ + class OrthancConnection; + + class Instance : public Orthanc::IDynamicObject + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value tags_; + std::auto_ptr reader_; + Orthanc::ImageExtractionMode mode_; + + void DownloadImage(); + + public: + Instance(const OrthancConnection& connection, + const std::string& id); + + void SetImageExtractionMode(Orthanc::ImageExtractionMode mode); + + Orthanc::ImageExtractionMode GetImageExtractionMode() const + { + return mode_; + } + + std::string GetTagAsString(const char* tag); + + float GetTagAsFloat(const char* tag); + + int GetTagAsInt(const char* tag); + + unsigned int GetWidth(); + + unsigned int GetHeight(); + + unsigned int GetPitch(); + + Orthanc::PixelFormat GetPixelFormat(); + + const void* GetBuffer(); + + const void* GetBuffer(unsigned int y); + + void DiscardImage(); + + void SplitVectorOfFloats(std::vector& target, + const char* tag); + }; +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/OrthancConnection.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancConnection.cpp Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "OrthancConnection.h" + +#include "../Core/OrthancException.h" + +namespace OrthancClient +{ + void OrthancConnection::ReadPatients() + { + client_.SetUrl(orthancUrl_ + "/patients"); + Json::Value v; + if (!client_.Apply(content_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index) + { + return new Patient(*this, content_[index].asString()); + } + + Patient& OrthancConnection::GetPatient(unsigned int index) + { + return dynamic_cast(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(); + } + + +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/OrthancConnection.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancConnection.h Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,97 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Core/HttpClient.h" + +#include "Patient.h" + +namespace OrthancClient +{ + class OrthancConnection : public boost::noncopyable, private Orthanc::ArrayFilledByThreads::IFiller + { + private: + Orthanc::HttpClient client_; + std::string orthancUrl_; + Orthanc::ArrayFilledByThreads patients_; + Json::Value content_; + + void ReadPatients(); + + virtual size_t GetFillerSize() + { + return content_.size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + OrthancConnection(const char* orthancUrl); + + OrthancConnection(const char* orthancUrl, + const char* username, + const char* password); + + unsigned int GetThreadCount() const + { + return patients_.GetThreadCount(); + } + + void SetThreadCount(unsigned int threadCount) + { + patients_.SetThreadCount(threadCount); + } + + void Reload() + { + patients_.Reload(); + } + + const Orthanc::HttpClient& GetHttpClient() const + { + return client_; + } + + const std::string& GetOrthancUrl() const + { + return orthancUrl_; + } + + unsigned int GetPatientCount() + { + return patients_.GetSize(); + } + + Patient& GetPatient(unsigned int index); + }; +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Patient.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Patient.cpp Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "Patient.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +namespace OrthancClient +{ + void Patient::ReadPatient() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(connection_.GetOrthancUrl() + "/patients/" + id_); + Json::Value v; + if (!client.Apply(patient_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index) + { + return new Study(connection_, patient_["Studies"][index].asString()); + } + + Patient::Patient(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + studies_(*this) + { + studies_.SetThreadCount(connection.GetThreadCount()); + ReadPatient(); + } + + std::string Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (patient_["MainDicomTags"].isMember(tag)) + { + return patient_["MainDicomTags"][tag].asString(); + } + else + { + return defaultValue; + } + } +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Patient.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Patient.h Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "Study.h" + +namespace OrthancClient +{ + class Patient : public Orthanc::IDynamicObject, private Orthanc::ArrayFilledByThreads::IFiller + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value patient_; + Orthanc::ArrayFilledByThreads studies_; + + void ReadPatient(); + + virtual size_t GetFillerSize() + { + return patient_["Studies"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + Patient(const OrthancConnection& connection, + const std::string& id); + + void Reload() + { + studies_.Reload(); + } + + unsigned int GetStudyCount() + { + return studies_.GetSize(); + } + + Study& GetStudy(unsigned int index) + { + return dynamic_cast(studies_.GetItem(index)); + } + + const std::string& GetId() const + { + return id_; + } + + std::string GetMainDicomTag(const char* tag, + const char* defaultValue) const; + }; +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Series.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Series.cpp Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,431 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "Series.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +#include +#include + +namespace OrthancClient +{ + namespace + { + class SliceLocator + { + private: + float normal_[3]; + + public: + SliceLocator(Instance& someSlice) + { + /** + * Compute the slice normal from Image Orientation Patient. + * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice + * http://www.itk.org/pipermail/insight-users/2003-September/004762.html + **/ + + std::vector cosines; + someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); + + if (cosines.size() != 6) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; + normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; + normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; + } + + + /** + * Compute the distance of some slice along the slice normal. + **/ + float ComputeSliceLocation(Instance& instance) const + { + std::vector ipp; + instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); + if (ipp.size() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + float dist = 0; + + for (int i = 0; i < 3; i++) + { + dist += normal_[i] * ipp[i]; + } + + return dist; + } + }; + + class ImageDownloadCommand : public Orthanc::ICommand + { + private: + Orthanc::PixelFormat format_; + Orthanc::ImageExtractionMode mode_; + Instance& instance_; + void* target_; + size_t lineStride_; + + public: + ImageDownloadCommand(Instance& instance, + Orthanc::PixelFormat format, + Orthanc::ImageExtractionMode mode, + void* target, + size_t lineStride) : + format_(format), + mode_(mode), + instance_(instance), + target_(target), + lineStride_(lineStride) + { + instance_.SetImageExtractionMode(mode); + } + + virtual bool Execute() + { + using namespace Orthanc; + + unsigned int width = instance_.GetHeight(); + + for (unsigned int y = 0; y < instance_.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast(target_) + y * lineStride_; + + if (instance_.GetPixelFormat() == format_) + { + memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth()); + } + else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 && + format_ == PixelFormat_RGB24) + { + const uint8_t* s = reinterpret_cast(instance_.GetBuffer(y)); + for (unsigned int x = 0; x < width; x++, s++, p += 3) + { + p[0] = *s; + p[1] = *s; + p[2] = *s; + } + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + + // Do not keep the image in memory, as we are loading 3D images + instance_.DiscardImage(); + + return true; + } + }; + } + + + void Series::Check3DImage() + { + if (!Is3DImage()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + bool Series::Is3DImageInternal() + { + try + { + if (GetInstanceCount() == 0) + { + return true; + } + + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + if (GetInstance(0).GetTagAsString("Columns") != GetInstance(i).GetTagAsString("Columns") || + GetInstance(0).GetTagAsString("Rows") != GetInstance(i).GetTagAsString("Rows") || + GetInstance(0).GetTagAsString("ImageOrientationPatient") != GetInstance(i).GetTagAsString("ImageOrientationPatient") || + GetInstance(0).GetTagAsString("SliceThickness") != GetInstance(i).GetTagAsString("SliceThickness") || + GetInstance(0).GetTagAsString("PixelSpacing") != GetInstance(i).GetTagAsString("PixelSpacing")) + { + return false; + } + } + + SliceLocator locator(GetInstance(0)); + std::set l; + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + l.insert(locator.ComputeSliceLocation(GetInstance(i))); + } + + return l.size() == GetInstanceCount(); + } + catch (Orthanc::OrthancException) + { + return false; + } + } + + void Series::ReadSeries() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + + client.SetUrl(connection_.GetOrthancUrl() + "/series/" + id_); + Json::Value v; + if (!client.Apply(series_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Series::GetFillerItem(size_t index) + { + return new Instance(connection_, series_["Instances"][index].asString()); + } + + Series::Series(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + instances_(*this) + { + ReadSeries(); + status_ = Status3DImage_NotTested; + + instances_.SetThreadCount(connection.GetThreadCount()); + } + + + bool Series::Is3DImage() + { + if (status_ == Status3DImage_NotTested) + { + status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False; + } + + return status_ == Status3DImage_True; + } + + unsigned int Series::GetInstanceCount() + { + return instances_.GetSize(); + } + + Instance& Series::GetInstance(unsigned int index) + { + return dynamic_cast(instances_.GetItem(index)); + } + + std::string Series::GetUrl() const + { + return connection_.GetOrthancUrl() + "/series/" + id_; + } + + unsigned int Series::GetWidth() + { + Check3DImage(); + + if (GetInstanceCount() == 0) + return 0; + else + return GetInstance(0).GetTagAsInt("Columns"); + } + + unsigned int Series::GetHeight() + { + Check3DImage(); + + if (GetInstanceCount() == 0) + return 0; + else + return GetInstance(0).GetTagAsInt("Rows"); + } + + void Series::GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ) + { + Check3DImage(); + + if (GetInstanceCount() == 0) + { + sizeX = 0; + sizeY = 0; + sizeZ = 0; + } + else + { + try + { + std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); + size_t pos = s.find('\\'); + assert(pos != std::string::npos); + std::string sy = s.substr(0, pos); + std::string sx = s.substr(pos + 1); + + sizeX = boost::lexical_cast(sx); + sizeY = boost::lexical_cast(sy); + sizeZ = GetInstance(0).GetTagAsFloat("SliceThickness"); + } + catch (boost::bad_lexical_cast) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + + std::string Series::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (series_["MainDicomTags"].isMember(tag)) + { + return series_["MainDicomTags"][tag].asString(); + } + else + { + return defaultValue; + } + } + + + + void Series::Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener* listener) + { + using namespace Orthanc; + + // Choose the extraction mode, depending on the format of the + // target image. + + uint8_t bytesPerPixel; + ImageExtractionMode mode; + + switch (format) + { + case PixelFormat_RGB24: + bytesPerPixel = 3; + mode = ImageExtractionMode_Preview; + break; + + case PixelFormat_Grayscale8: + bytesPerPixel = 1; + mode = ImageExtractionMode_UInt8; // Preview ??? + break; + + case PixelFormat_Grayscale16: + bytesPerPixel = 2; + mode = ImageExtractionMode_UInt16; + break; + + case PixelFormat_SignedGrayscale16: + bytesPerPixel = 2; + mode = ImageExtractionMode_UInt16; + format = PixelFormat_Grayscale16; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + + // Check that the target image is properly sized + unsigned int sx = GetWidth(); + unsigned int sy = GetHeight(); + + if (lineStride < sx * bytesPerPixel || + stackStride < sx * sy * bytesPerPixel) + { + throw OrthancException(ErrorCode_BadRequest); + } + + if (sx == 0 || sy == 0 || GetInstanceCount() == 0) + { + // Empty image, nothing to do + if (listener) + listener->SignalSuccess(0); + return; + } + + + /** + * Order the stacks according to their distance along the slice + * normal (using the "Image Position Patient" tag). This works + * even if the "SliceLocation" tag is absent. + **/ + SliceLocator locator(GetInstance(0)); + + typedef std::map Instances; + Instances instances; + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + float dist = locator.ComputeSliceLocation(GetInstance(i)); + instances[dist] = &GetInstance(i); + } + + if (instances.size() != GetInstanceCount()) + { + // Several instances have the same Z coordinate + throw OrthancException(ErrorCode_NotImplemented); + } + + + // Submit the download of each stack as a set of commands + ThreadedCommandProcessor processor(connection_.GetThreadCount()); + + if (listener != NULL) + { + processor.SetListener(*listener); + } + + uint8_t* stackTarget = reinterpret_cast(target); + for (Instances::iterator it = instances.begin(); it != instances.end(); it++) + { + processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride)); + stackTarget += stackStride; + } + + + // Wait for all the stacks to be downloaded + if (!processor.Join()) + { + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Series.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Series.h Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,125 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "Instance.h" + +#include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/ThreadedCommandProcessor.h" + +namespace OrthancClient +{ + class Series : public Orthanc::IDynamicObject, private Orthanc::ArrayFilledByThreads::IFiller + { + private: + enum Status3DImage + { + Status3DImage_NotTested, + Status3DImage_True, + Status3DImage_False + }; + + const OrthancConnection& connection_; + std::string id_; + Json::Value series_; + Orthanc::ArrayFilledByThreads instances_; + Status3DImage status_; + + void Check3DImage(); + + bool Is3DImageInternal(); + + void ReadSeries(); + + virtual size_t GetFillerSize() + { + return series_["Instances"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + void Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener* listener); + + public: + Series(const OrthancConnection& connection, + const std::string& id); + + void Reload() + { + instances_.Reload(); + } + + bool Is3DImage(); + + unsigned int GetInstanceCount(); + + Instance& GetInstance(unsigned int index); + + const std::string& GetId() const + { + return id_; + } + + std::string GetUrl() const; + + unsigned int GetWidth(); + + unsigned int GetHeight(); + + void GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ); + + std::string GetMainDicomTag(const char* tag, + const char* defaultValue) const; + + void Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener& listener) + { + Load3DImage(target, format, lineStride, stackStride, &listener); + } + + void Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride) + { + Load3DImage(target, format, lineStride, stackStride, NULL); + } + }; +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Study.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Study.cpp Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "Study.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +namespace OrthancClient +{ + void Study::ReadStudy() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(connection_.GetOrthancUrl() + "/studies/" + id_); + Json::Value v; + if (!client.Apply(study_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Study::GetFillerItem(size_t index) + { + return new Series(connection_, study_["Series"][index].asString()); + } + + Study::Study(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + series_(*this) + { + series_.SetThreadCount(connection.GetThreadCount()); + ReadStudy(); + } + + std::string Study::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (study_["MainDicomTags"].isMember(tag)) + { + return study_["MainDicomTags"][tag].asString(); + } + else + { + return defaultValue; + } + } +} diff -r 888f8a778e70 -r 0cd977e94479 OrthancCppClient/Study.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Study.h Tue Jul 16 09:08:09 2013 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "Series.h" + +namespace OrthancClient +{ + class Study : public Orthanc::IDynamicObject, private Orthanc::ArrayFilledByThreads::IFiller + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value study_; + Orthanc::ArrayFilledByThreads series_; + + void ReadStudy(); + + virtual size_t GetFillerSize() + { + return study_["Series"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + Study(const OrthancConnection& connection, + const std::string& id); + + void Reload() + { + series_.Reload(); + } + + unsigned int GetSeriesCount() + { + return series_.GetSize(); + } + + Series& GetSeries(unsigned int index) + { + return dynamic_cast(series_.GetItem(index)); + } + + const std::string& GetId() const + { + return id_; + } + + std::string GetMainDicomTag(const char* tag, + const char* defaultValue) const; + }; +} diff -r 888f8a778e70 -r 0cd977e94479 README --- a/README Tue Jul 16 09:00:25 2013 +0200 +++ b/README Tue Jul 16 09:08:09 2013 +0200 @@ -60,10 +60,6 @@ The following directories have separate licensing terms: -* The files of the "OrthancCppClient/" directory are licensed under - the MIT license, which allows commercial products to statically link - against the C++ client library. - * The file of the "Core/SQLite/" directory are licensed under the 3-clause BSD license, as they are derived from the Chromium project. @@ -74,7 +70,7 @@ This archive contains the following directories: * Core/ - The core C++ classes (independent of DCMTK) -* OrthancCppClient/ - Code of the C++ client (under MIT license) +* OrthancCppClient/ - Code of the C++ client * OrthancExplorer/ - Code of the Web application (HTML5/Javascript) * OrthancServer/ - Code of the Orthanc server (depends on DCMTK) * Resources/ - Scripts, resources for building third-party code