Mercurial > hg > orthanc
changeset 556:b26a7c397c34 find-move-scp
merge
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 18 Sep 2013 15:27:07 +0200 |
parents | f6e61e329bbf (current diff) 11fee43c5861 (diff) |
children | e0cfb413c86b |
files | Core/Cache/CacheIndex.h Core/PngWriter.cpp Core/PngWriter.h OrthancCppClient/CMakeLists.txt OrthancCppClient/HttpClient.cpp OrthancCppClient/HttpClient.h OrthancCppClient/HttpEnumerations.h OrthancCppClient/HttpException.cpp OrthancCppClient/HttpException.h OrthancCppClient/main.cpp UnitTests/PngWriter.cpp |
diffstat | 208 files changed, 8863 insertions(+), 2246 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Mon Apr 29 12:48:10 2013 +0200 +++ b/CMakeLists.txt Wed Sep 18 15:27:07 2013 +0200 @@ -9,27 +9,25 @@ # Parameters of the build SET(STATIC_BUILD ON CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") -SET(STANDALONE_BUILD OFF CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") +SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") SET(ENABLE_SSL ON CACHE BOOL "Include support for SSL") SET(BUILD_UNIT_TESTS ON CACHE BOOL "Build the unit tests") +SET(DCMTK_DICTIONARY_DIR "/usr/share/dcmtk" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (ignored in standalone builds)") -# Advanced parameters (for Debian packaging) -SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp (only for Debian sid)") +# Advanced parameters to fine-tune linking against system libraries +SET(USE_DYNAMIC_JSONCPP OFF CACHE BOOL "Use the dynamic version of JsonCpp") SET(USE_DYNAMIC_GOOGLE_LOG ON CACHE BOOL "Use the dynamic version of Google Log") -SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test (not for Debian sid)") +SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test") SET(USE_DYNAMIC_SQLITE ON CACHE BOOL "Use the dynamic version of SQLite") SET(USE_DYNAMIC_MONGOOSE OFF CACHE BOOL "Use the dynamic version of Mongoose") -SET(DEBIAN_FORCE_HARDENING OFF CACHE BOOL "Force the injection of Debian hardening flags (unrecommended)") -SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (only for Debian sid)") -SET(ONLY_CORE_LIBRARY OFF CACHE BOOL "Only build the core library") +SET(USE_DYNAMIC_LUA OFF CACHE BOOL "Use the dynamic version of Lua") +SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") mark_as_advanced(USE_DYNAMIC_JSONCPP) mark_as_advanced(USE_DYNAMIC_GOOGLE_LOG) mark_as_advanced(USE_DYNAMIC_GOOGLE_TEST) mark_as_advanced(USE_DYNAMIC_SQLITE) -mark_as_advanced(DEBIAN_FORCE_HARDENING) -mark_as_advanced(DEBIAN_USE_STATIC_GOOGLE_TEST) -mark_as_advanced(ONLY_CORE_LIBRARY) +mark_as_advanced(DEBIAN_USE_GTEST_SOURCE_PACKAGE) # Some basic inclusions include(CheckIncludeFiles) @@ -62,23 +60,21 @@ endif() include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake) - -if(NOT ONLY_CORE_LIBRARY) - include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake) -endif() - +include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake) # Prepare the embedded files set(EMBEDDED_FILES PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json + LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua ) if (${STANDALONE_BUILD}) @@ -119,10 +115,12 @@ Core/DicomFormat/DicomTag.cpp Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomInstanceHasher.cpp + Core/Enumerations.cpp Core/FileStorage/FileStorage.cpp Core/FileStorage/StorageAccessor.cpp Core/FileStorage/CompressedFileStorageAccessor.cpp Core/FileStorage/FileStorageAccessor.cpp + Core/HttpClient.cpp Core/HttpServer/EmbeddedResourceHttpHandler.cpp Core/HttpServer/FilesystemHttpHandler.cpp Core/HttpServer/HttpHandler.cpp @@ -134,7 +132,11 @@ Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApi.cpp Core/MultiThreading/BagOfRunnablesBySteps.cpp - Core/PngWriter.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 Core/SQLite/FunctionContext.cpp Core/SQLite/Statement.cpp @@ -143,68 +145,71 @@ Core/SQLite/Transaction.cpp Core/Toolbox.cpp Core/Uuid.cpp + Core/Lua/LuaContext.cpp + Core/Lua/LuaFunctionCall.cpp - OrthancCppClient/HttpClient.cpp - OrthancCppClient/HttpException.cpp + OrthancCppClient/OrthancConnection.cpp + OrthancCppClient/Study.cpp + OrthancCppClient/Series.cpp + OrthancCppClient/Instance.cpp + OrthancCppClient/Patient.cpp ) +add_library(ServerLibrary + STATIC + ${DCMTK_SOURCES} + OrthancServer/DicomProtocol/DicomFindAnswers.cpp + OrthancServer/DicomProtocol/DicomServer.cpp + OrthancServer/DicomProtocol/DicomUserConnection.cpp + OrthancServer/FromDcmtkBridge.cpp + OrthancServer/Internals/CommandDispatcher.cpp + OrthancServer/Internals/FindScp.cpp + OrthancServer/Internals/MoveScp.cpp + OrthancServer/Internals/StoreScp.cpp + OrthancServer/OrthancInitialization.cpp + OrthancServer/OrthancRestApi.cpp + OrthancServer/ServerIndex.cpp + OrthancServer/ToDcmtkBridge.cpp + OrthancServer/DatabaseWrapper.cpp + OrthancServer/ServerContext.cpp + OrthancServer/ServerEnumerations.cpp + OrthancServer/ServerToolbox.cpp + ) -if(NOT ONLY_CORE_LIBRARY) - add_library(ServerLibrary - STATIC - ${DCMTK_SOURCES} - OrthancServer/DicomProtocol/DicomFindAnswers.cpp - OrthancServer/DicomProtocol/DicomServer.cpp - OrthancServer/DicomProtocol/DicomUserConnection.cpp - OrthancServer/FromDcmtkBridge.cpp - OrthancServer/Internals/CommandDispatcher.cpp - OrthancServer/Internals/FindScp.cpp - OrthancServer/Internals/MoveScp.cpp - OrthancServer/Internals/StoreScp.cpp - OrthancServer/OrthancInitialization.cpp - OrthancServer/OrthancRestApi.cpp - OrthancServer/ServerIndex.cpp - OrthancServer/ToDcmtkBridge.cpp - OrthancServer/DatabaseWrapper.cpp - OrthancServer/ServerContext.cpp - OrthancServer/ServerEnumerations.cpp - OrthancServer/ServerToolbox.cpp - ) - - # Ensure autogenerated code is built before building ServerLibrary - add_dependencies(ServerLibrary CoreLibrary) +# Ensure autogenerated code is built before building ServerLibrary +add_dependencies(ServerLibrary CoreLibrary) - add_executable(Orthanc - OrthancServer/main.cpp - ) +add_executable(Orthanc + OrthancServer/main.cpp + ) - target_link_libraries(Orthanc ServerLibrary CoreLibrary) +target_link_libraries(Orthanc ServerLibrary CoreLibrary) - install( - TARGETS Orthanc - RUNTIME DESTINATION bin - ) +install( + TARGETS Orthanc + RUNTIME DESTINATION bin + ) - # Build the unit tests if required - if (BUILD_UNIT_TESTS) - add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1) - include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) - add_executable(UnitTests - ${GTEST_SOURCES} - UnitTests/FileStorage.cpp - UnitTests/MemoryCache.cpp - UnitTests/PngWriter.cpp - UnitTests/RestApi.cpp - UnitTests/SQLite.cpp - UnitTests/SQLiteChromium.cpp - UnitTests/ServerIndex.cpp - UnitTests/Versions.cpp - UnitTests/Zip.cpp - UnitTests/main.cpp - ) - target_link_libraries(UnitTests ServerLibrary CoreLibrary) - endif() +# Build the unit tests if required +if (BUILD_UNIT_TESTS) + add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1) + include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) + add_executable(UnitTests + ${GTEST_SOURCES} + UnitTests/FileStorage.cpp + UnitTests/MemoryCache.cpp + UnitTests/Png.cpp + UnitTests/RestApi.cpp + UnitTests/SQLite.cpp + UnitTests/SQLiteChromium.cpp + UnitTests/ServerIndex.cpp + UnitTests/Versions.cpp + UnitTests/Zip.cpp + UnitTests/Lua.cpp + UnitTests/main.cpp + ) + target_link_libraries(UnitTests ServerLibrary CoreLibrary) endif()
--- a/Core/Cache/CacheIndex.h Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,250 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <list> -#include <map> -#include <boost/noncopyable.hpp> -#include <cassert> - -#include "../OrthancException.h" -#include "../Toolbox.h" - -namespace Orthanc -{ - /** - * This class implements the index of a cache with least recently - * used (LRU) recycling policy. All the items of the cache index - * can be associated with a payload. - * Reference: http://stackoverflow.com/a/2504317 - **/ - template <typename T, typename Payload = NullType> - class CacheIndex : public boost::noncopyable - { - private: - typedef std::list< std::pair<T, Payload> > Queue; - typedef std::map<T, typename Queue::iterator> Index; - - Index index_; - Queue queue_; - - /** - * Internal method for debug builds to check whether the internal - * data structures are not corrupted. - **/ - void CheckInvariants() const; - - public: - /** - * Add a new element to the cache index, and make it the most - * recent element. - * \param id The ID of the element. - * \param payload The payload of the element. - **/ - void Add(T id, Payload payload = Payload()); - - /** - * When accessing an element of the cache, this method tags the - * element as the most recently used. - * \param id The most recently accessed item. - **/ - void TagAsMostRecent(T id); - - /** - * Remove an element from the cache index. - * \param id The item to remove. - **/ - Payload Invalidate(T id); - - /** - * Get the oldest element in the cache and remove it. - * \return The oldest item. - **/ - T RemoveOldest() - { - Payload p; - return RemoveOldest(p); - } - - /** - * Get the oldest element in the cache, remove it and return the - * associated payload. - * \param payload Where to store the associated payload. - * \return The oldest item. - **/ - T RemoveOldest(Payload& payload); - - /** - * Check whether an element is contained in the cache. - * \param id The item. - * \return \c true iff the item is indexed by the cache. - **/ - bool Contains(T id) const - { - return index_.find(id) != index_.end(); - } - - bool Contains(T id, Payload& payload) const - { - typename Index::const_iterator it = index_.find(id); - if (it == index_.end()) - { - return false; - } - else - { - payload = it->second->second; - return true; - } - } - - /** - * Return the number of elements in the cache. - * \return The number of elements. - **/ - size_t GetSize() const - { - assert(index_.size() == queue_.size()); - return queue_.size(); - } - - /** - * Check whether the cache index is empty. - * \return \c true iff the cache is empty. - **/ - bool IsEmpty() const - { - return index_.empty(); - } - }; - - - - - /****************************************************************** - ** Implementation of the template - ******************************************************************/ - - template <typename T, typename Payload> - void CacheIndex<T, Payload>::CheckInvariants() const - { -#ifndef NDEBUG - assert(index_.size() == queue_.size()); - - for (typename Index::const_iterator - it = index_.begin(); it != index_.end(); it++) - { - assert(it->second != queue_.end()); - assert(it->second->first == it->first); - } -#endif - } - - - template <typename T, typename Payload> - void CacheIndex<T, Payload>::Add(T id, Payload payload) - { - if (Contains(id)) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - queue_.push_front(std::make_pair(id, payload)); - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template <typename T, typename Payload> - void CacheIndex<T, Payload>::TagAsMostRecent(T id) - { - if (!Contains(id)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - typename Index::iterator it = index_.find(id); - assert(it != index_.end()); - - std::pair<T, Payload> item = *(it->second); - - queue_.erase(it->second); - queue_.push_front(item); - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template <typename T, typename Payload> - Payload CacheIndex<T, Payload>::Invalidate(T id) - { - if (!Contains(id)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - typename Index::iterator it = index_.find(id); - assert(it != index_.end()); - - Payload payload = it->second->second; - queue_.erase(it->second); - index_.erase(it); - - CheckInvariants(); - return payload; - } - - - template <typename T, typename Payload> - T CacheIndex<T, Payload>::RemoveOldest(Payload& payload) - { - if (IsEmpty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - std::pair<T, Payload> item = queue_.back(); - T oldest = item.first; - payload = item.second; - - queue_.pop_back(); - assert(index_.find(oldest) != index_.end()); - index_.erase(oldest); - - CheckInvariants(); - - return oldest; - } -}
--- a/Core/Cache/ICachePageProvider.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Cache/ICachePageProvider.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/LeastRecentlyUsedIndex.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,346 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <list> +#include <map> +#include <boost/noncopyable.hpp> +#include <cassert> + +#include "../OrthancException.h" +#include "../Toolbox.h" + +namespace Orthanc +{ + /** + * This class implements the index of a cache with least recently + * used (LRU) recycling policy. All the items of the cache index + * can be associated with a payload. + * Reference: http://stackoverflow.com/a/2504317 + **/ + template <typename T, typename Payload = NullType> + class LeastRecentlyUsedIndex : public boost::noncopyable + { + private: + typedef std::list< std::pair<T, Payload> > Queue; + typedef std::map<T, typename Queue::iterator> Index; + + Index index_; + Queue queue_; + + /** + * Internal method for debug builds to check whether the internal + * data structures are not corrupted. + **/ + void CheckInvariants() const; + + public: + /** + * Add a new element to the cache index, and make it the most + * recent element. + * \param id The ID of the element. + * \param payload The payload of the element. + **/ + void Add(T id, Payload payload = Payload()); + + void AddOrMakeMostRecent(T id, Payload payload = Payload()); + + /** + * When accessing an element of the cache, this method tags the + * element as the most recently used. + * \param id The most recently accessed item. + **/ + void MakeMostRecent(T id); + + void MakeMostRecent(T id, Payload updatedPayload); + + /** + * Remove an element from the cache index. + * \param id The item to remove. + **/ + Payload Invalidate(T id); + + /** + * Get the oldest element in the cache and remove it. + * \return The oldest item. + **/ + T RemoveOldest(); + + /** + * Get the oldest element in the cache, remove it and return the + * associated payload. + * \param payload Where to store the associated payload. + * \return The oldest item. + **/ + T RemoveOldest(Payload& payload); + + /** + * Check whether an element is contained in the cache. + * \param id The item. + * \return \c true iff the item is indexed by the cache. + **/ + bool Contains(T id) const + { + return index_.find(id) != index_.end(); + } + + bool Contains(T id, Payload& payload) const + { + typename Index::const_iterator it = index_.find(id); + if (it == index_.end()) + { + return false; + } + else + { + payload = it->second->second; + return true; + } + } + + /** + * Return the number of elements in the cache. + * \return The number of elements. + **/ + size_t GetSize() const + { + assert(index_.size() == queue_.size()); + return queue_.size(); + } + + /** + * Check whether the cache index is empty. + * \return \c true iff the cache is empty. + **/ + bool IsEmpty() const + { + return index_.empty(); + } + + const T& GetOldest() const; + + const Payload& GetOldestPayload() const; + }; + + + + + /****************************************************************** + ** Implementation of the template + ******************************************************************/ + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::CheckInvariants() const + { +#ifndef NDEBUG + assert(index_.size() == queue_.size()); + + for (typename Index::const_iterator + it = index_.begin(); it != index_.end(); it++) + { + assert(it->second != queue_.end()); + assert(it->second->first == it->first); + } +#endif + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::Add(T id, Payload payload) + { + if (Contains(id)) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + queue_.push_front(std::make_pair(id, payload)); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + std::pair<T, Payload> item = *(it->second); + + queue_.erase(it->second); + queue_.push_front(item); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::AddOrMakeMostRecent(T id, Payload payload) + { + typename Index::iterator it = index_.find(id); + + if (it != index_.end()) + { + // Already existing. Make it most recent. + std::pair<T, Payload> item = *(it->second); + item.second = payload; + queue_.erase(it->second); + queue_.push_front(item); + } + else + { + // New item + queue_.push_front(std::make_pair(id, payload)); + } + + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id, Payload updatedPayload) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + std::pair<T, Payload> item = *(it->second); + item.second = updatedPayload; + + queue_.erase(it->second); + queue_.push_front(item); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template <typename T, typename Payload> + Payload LeastRecentlyUsedIndex<T, Payload>::Invalidate(T id) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + Payload payload = it->second->second; + queue_.erase(it->second); + index_.erase(it); + + CheckInvariants(); + return payload; + } + + + template <typename T, typename Payload> + T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest(Payload& payload) + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::pair<T, Payload> item = queue_.back(); + T oldest = item.first; + payload = item.second; + + queue_.pop_back(); + assert(index_.find(oldest) != index_.end()); + index_.erase(oldest); + + CheckInvariants(); + + return oldest; + } + + + template <typename T, typename Payload> + T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest() + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::pair<T, Payload> item = queue_.back(); + T oldest = item.first; + + queue_.pop_back(); + assert(index_.find(oldest) != index_.end()); + index_.erase(oldest); + + CheckInvariants(); + + return oldest; + } + + + template <typename T, typename Payload> + const T& LeastRecentlyUsedIndex<T, Payload>::GetOldest() const + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + return queue_.back().first; + } + + + template <typename T, typename Payload> + const Payload& LeastRecentlyUsedIndex<T, Payload>::GetOldestPayload() const + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + return queue_.back().second; + } +}
--- a/Core/Cache/MemoryCache.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Cache/MemoryCache.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -46,7 +46,7 @@ { VLOG(1) << "Reusing a cache page"; assert(p != NULL); - index_.TagAsMostRecent(id); + index_.MakeMostRecent(id); return *p; }
--- a/Core/Cache/MemoryCache.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Cache/MemoryCache.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,7 +33,7 @@ #pragma once #include <memory> -#include "CacheIndex.h" +#include "LeastRecentlyUsedIndex.h" #include "ICachePageProvider.h" namespace Orthanc @@ -52,7 +52,7 @@ ICachePageProvider& provider_; size_t cacheSize_; - CacheIndex<std::string, Page*> index_; + LeastRecentlyUsedIndex<std::string, Page*> index_; Page& Load(const std::string& id);
--- a/Core/ChunkedBuffer.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/ChunkedBuffer.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/ChunkedBuffer.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/ChunkedBuffer.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/BufferCompressor.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/BufferCompressor.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/HierarchicalZipWriter.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/HierarchicalZipWriter.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZipWriter.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/ZipWriter.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZipWriter.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/ZipWriter.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/ZlibCompressor.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Compression/ZlibCompressor.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomArray.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomArray.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomElement.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomElement.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -231,25 +231,28 @@ pixel += channel * frameOffset_ / samplesPerPixel_ + x * bytesPerPixel_; } - int32_t v; + uint32_t v; v = pixel[0]; if (bytesPerPixel_ >= 2) - v = v + (static_cast<int32_t>(pixel[1]) << 8); + v = v + (static_cast<uint32_t>(pixel[1]) << 8); if (bytesPerPixel_ >= 3) - v = v + (static_cast<int32_t>(pixel[2]) << 16); + v = v + (static_cast<uint32_t>(pixel[2]) << 16); if (bytesPerPixel_ >= 4) - v = v + (static_cast<int32_t>(pixel[3]) << 24); + v = v + (static_cast<uint32_t>(pixel[3]) << 24); - v = (v >> shift_) & mask_; + v = v >> shift_; if (v & signMask_) { - // Signed value: Not implemented yet - //throw OrthancException(ErrorCode_NotImplemented); - v = 0; + // Signed value + // http://en.wikipedia.org/wiki/Two%27s_complement#Subtraction_from_2N + return -static_cast<int32_t>(mask_) + static_cast<int32_t>(v & mask_) - 1; } - - return v; + else + { + // Unsigned value + return static_cast<int32_t>(v & mask_); + } }
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomMap.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -76,9 +76,11 @@ DicomTag(0x0018, 0x0024), // SequenceName DicomTag(0x0018, 0x1030), // ProtocolName DicomTag(0x0020, 0x0011), // SeriesNumber - //DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, + DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, DICOM_TAG_IMAGES_IN_ACQUISITION, + DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, DICOM_TAG_NUMBER_OF_SLICES, + DICOM_TAG_NUMBER_OF_TIME_SLICES, DICOM_TAG_SERIES_INSTANCE_UID }; @@ -90,6 +92,7 @@ DICOM_TAG_IMAGE_INDEX, DICOM_TAG_INSTANCE_NUMBER, DICOM_TAG_NUMBER_OF_FRAMES, + DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, DICOM_TAG_SOP_INSTANCE_UID };
--- a/Core/DicomFormat/DicomMap.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomMap.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomNullValue.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomNullValue.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomString.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomString.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomTag.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -95,6 +95,7 @@ static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013); static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081); + static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101); static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090); static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002); @@ -105,4 +106,8 @@ static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002); static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003); + + // DICOM tags used for fMRI (thanks to Will Ryder) + static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105); + static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100); }
--- a/Core/DicomFormat/DicomValue.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/DicomFormat/DicomValue.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/EnumerationDictionary.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,122 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OrthancException.h" + +#include <boost/lexical_cast.hpp> +#include <string> +#include <map> + +namespace Orthanc +{ + namespace Toolbox + { + template <typename Enumeration> + class EnumerationDictionary + { + private: + typedef std::map<Enumeration, std::string> EnumerationToString; + typedef std::map<std::string, Enumeration> StringToEnumeration; + + EnumerationToString enumerationToString_; + StringToEnumeration stringToEnumeration_; + + public: + void Add(Enumeration value, const std::string& str) + { + // Check if these values are free + if (enumerationToString_.find(value) != enumerationToString_.end() || + stringToEnumeration_.find(str) != stringToEnumeration_.end()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + // Prevent the registration of a number + try + { + boost::lexical_cast<int>(str); + throw OrthancException(ErrorCode_BadRequest); + } + catch (boost::bad_lexical_cast) + { + // OK, the string is not a number + } + + enumerationToString_[value] = str; + stringToEnumeration_[str] = value; + stringToEnumeration_[boost::lexical_cast<std::string>(static_cast<int>(value))] = value; + } + + Enumeration Translate(const std::string& str) const + { + try + { + int value = boost::lexical_cast<int>(str); + return static_cast<Enumeration>(value); + } + catch (boost::bad_lexical_cast) + { + } + + typename StringToEnumeration::const_iterator + found = stringToEnumeration_.find(str); + + if (found == stringToEnumeration_.end()) + { + throw OrthancException(ErrorCode_InexistentItem); + } + else + { + return found->second; + } + } + + std::string Translate(Enumeration e) const + { + typename EnumerationToString::const_iterator + found = enumerationToString_.find(e); + + if (found == enumerationToString_.end()) + { + // No name for this item + return boost::lexical_cast<std::string>(static_cast<int>(e)); + } + else + { + return found->second; + } + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Enumerations.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,225 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "Enumerations.h" + +#include "OrthancException.h" + +namespace Orthanc +{ + const char* EnumerationToString(HttpMethod method) + { + switch (method) + { + case HttpMethod_Get: + return "GET"; + + case HttpMethod_Post: + return "POST"; + + case HttpMethod_Delete: + return "DELETE"; + + case HttpMethod_Put: + return "PUT"; + + default: + return "?"; + } + } + + + const char* EnumerationToString(HttpStatus status) + { + switch (status) + { + case HttpStatus_100_Continue: + return "Continue"; + + case HttpStatus_101_SwitchingProtocols: + return "Switching Protocols"; + + case HttpStatus_102_Processing: + return "Processing"; + + case HttpStatus_200_Ok: + return "OK"; + + case HttpStatus_201_Created: + return "Created"; + + case HttpStatus_202_Accepted: + return "Accepted"; + + case HttpStatus_203_NonAuthoritativeInformation: + return "Non-Authoritative Information"; + + case HttpStatus_204_NoContent: + return "No Content"; + + case HttpStatus_205_ResetContent: + return "Reset Content"; + + case HttpStatus_206_PartialContent: + return "Partial Content"; + + case HttpStatus_207_MultiStatus: + return "Multi-Status"; + + case HttpStatus_208_AlreadyReported: + return "Already Reported"; + + case HttpStatus_226_IMUsed: + return "IM Used"; + + case HttpStatus_300_MultipleChoices: + return "Multiple Choices"; + + case HttpStatus_301_MovedPermanently: + return "Moved Permanently"; + + case HttpStatus_302_Found: + return "Found"; + + case HttpStatus_303_SeeOther: + return "See Other"; + + case HttpStatus_304_NotModified: + return "Not Modified"; + + case HttpStatus_305_UseProxy: + return "Use Proxy"; + + case HttpStatus_307_TemporaryRedirect: + return "Temporary Redirect"; + + case HttpStatus_400_BadRequest: + return "Bad Request"; + + case HttpStatus_401_Unauthorized: + return "Unauthorized"; + + case HttpStatus_402_PaymentRequired: + return "Payment Required"; + + case HttpStatus_403_Forbidden: + return "Forbidden"; + + case HttpStatus_404_NotFound: + return "Not Found"; + + case HttpStatus_405_MethodNotAllowed: + return "Method Not Allowed"; + + case HttpStatus_406_NotAcceptable: + return "Not Acceptable"; + + case HttpStatus_407_ProxyAuthenticationRequired: + return "Proxy Authentication Required"; + + case HttpStatus_408_RequestTimeout: + return "Request Timeout"; + + case HttpStatus_409_Conflict: + return "Conflict"; + + case HttpStatus_410_Gone: + return "Gone"; + + case HttpStatus_411_LengthRequired: + return "Length Required"; + + case HttpStatus_412_PreconditionFailed: + return "Precondition Failed"; + + case HttpStatus_413_RequestEntityTooLarge: + return "Request Entity Too Large"; + + case HttpStatus_414_RequestUriTooLong: + return "Request-URI Too Long"; + + case HttpStatus_415_UnsupportedMediaType: + return "Unsupported Media Type"; + + case HttpStatus_416_RequestedRangeNotSatisfiable: + return "Requested Range Not Satisfiable"; + + case HttpStatus_417_ExpectationFailed: + return "Expectation Failed"; + + case HttpStatus_422_UnprocessableEntity: + return "Unprocessable Entity"; + + case HttpStatus_423_Locked: + return "Locked"; + + case HttpStatus_424_FailedDependency: + return "Failed Dependency"; + + case HttpStatus_426_UpgradeRequired: + return "Upgrade Required"; + + case HttpStatus_500_InternalServerError: + return "Internal Server Error"; + + case HttpStatus_501_NotImplemented: + return "Not Implemented"; + + case HttpStatus_502_BadGateway: + return "Bad Gateway"; + + case HttpStatus_503_ServiceUnavailable: + return "Service Unavailable"; + + case HttpStatus_504_GatewayTimeout: + return "Gateway Timeout"; + + case HttpStatus_505_HttpVersionNotSupported: + return "HTTP Version Not Supported"; + + case HttpStatus_506_VariantAlsoNegotiates: + return "Variant Also Negotiates"; + + case HttpStatus_507_InsufficientStorage: + return "Insufficient Storage"; + + case HttpStatus_509_BandwidthLimitExceeded: + return "Bandwidth Limit Exceeded"; + + case HttpStatus_510_NotExtended: + return "Not Extended"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } +}
--- a/Core/Enumerations.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Enumerations.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,10 +32,15 @@ #pragma once -#include "../OrthancCppClient/HttpEnumerations.h" - namespace Orthanc { + enum Endianness + { + Endianness_Unknown, + Endianness_Big, + Endianness_Little + }; + enum ErrorCode { // Generic error codes @@ -49,6 +54,7 @@ ErrorCode_BadSequenceOfCalls, ErrorCode_InexistentItem, ErrorCode_BadRequest, + ErrorCode_NetworkProtocol, // Specific error codes ErrorCode_UriSyntax, @@ -65,7 +71,97 @@ { PixelFormat_RGB24, PixelFormat_Grayscale8, - PixelFormat_Grayscale16 + PixelFormat_Grayscale16, + PixelFormat_SignedGrayscale16 + }; + + enum ImageExtractionMode + { + ImageExtractionMode_Preview, + ImageExtractionMode_UInt8, + ImageExtractionMode_UInt16, + ImageExtractionMode_Int16 + }; + + + /** + * Most common, non-joke and non-experimental HTTP status codes + * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + **/ + enum HttpStatus + { + HttpStatus_None = -1, + + // 1xx Informational + HttpStatus_100_Continue = 100, + HttpStatus_101_SwitchingProtocols = 101, + HttpStatus_102_Processing = 102, + + // 2xx Success + HttpStatus_200_Ok = 200, + HttpStatus_201_Created = 201, + HttpStatus_202_Accepted = 202, + HttpStatus_203_NonAuthoritativeInformation = 203, + HttpStatus_204_NoContent = 204, + HttpStatus_205_ResetContent = 205, + HttpStatus_206_PartialContent = 206, + HttpStatus_207_MultiStatus = 207, + HttpStatus_208_AlreadyReported = 208, + HttpStatus_226_IMUsed = 226, + + // 3xx Redirection + HttpStatus_300_MultipleChoices = 300, + HttpStatus_301_MovedPermanently = 301, + HttpStatus_302_Found = 302, + HttpStatus_303_SeeOther = 303, + HttpStatus_304_NotModified = 304, + HttpStatus_305_UseProxy = 305, + HttpStatus_307_TemporaryRedirect = 307, + + // 4xx Client Error + HttpStatus_400_BadRequest = 400, + HttpStatus_401_Unauthorized = 401, + HttpStatus_402_PaymentRequired = 402, + HttpStatus_403_Forbidden = 403, + HttpStatus_404_NotFound = 404, + HttpStatus_405_MethodNotAllowed = 405, + HttpStatus_406_NotAcceptable = 406, + HttpStatus_407_ProxyAuthenticationRequired = 407, + HttpStatus_408_RequestTimeout = 408, + HttpStatus_409_Conflict = 409, + HttpStatus_410_Gone = 410, + HttpStatus_411_LengthRequired = 411, + HttpStatus_412_PreconditionFailed = 412, + HttpStatus_413_RequestEntityTooLarge = 413, + HttpStatus_414_RequestUriTooLong = 414, + HttpStatus_415_UnsupportedMediaType = 415, + HttpStatus_416_RequestedRangeNotSatisfiable = 416, + HttpStatus_417_ExpectationFailed = 417, + HttpStatus_422_UnprocessableEntity = 422, + HttpStatus_423_Locked = 423, + HttpStatus_424_FailedDependency = 424, + HttpStatus_426_UpgradeRequired = 426, + + // 5xx Server Error + HttpStatus_500_InternalServerError = 500, + HttpStatus_501_NotImplemented = 501, + HttpStatus_502_BadGateway = 502, + HttpStatus_503_ServiceUnavailable = 503, + HttpStatus_504_GatewayTimeout = 504, + HttpStatus_505_HttpVersionNotSupported = 505, + HttpStatus_506_VariantAlsoNegotiates = 506, + HttpStatus_507_InsufficientStorage = 507, + HttpStatus_509_BandwidthLimitExceeded = 509, + HttpStatus_510_NotExtended = 510 + }; + + + enum HttpMethod + { + HttpMethod_Get = 0, + HttpMethod_Post = 1, + HttpMethod_Delete = 2, + HttpMethod_Put = 3 }; @@ -86,4 +182,10 @@ FileContentType_Dicom = 1, FileContentType_Json = 2 }; + + + + const char* EnumerationToString(HttpMethod method); + + const char* EnumerationToString(HttpStatus status); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngReader.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,305 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PngReader.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include <png.h> +#include <string.h> // For memcpy() + +namespace Orthanc +{ + namespace + { + struct FileRabi + { + FILE* fp_; + + FileRabi(const char* filename) + { + fp_ = fopen(filename, "rb"); + if (!fp_) + { + throw OrthancException(ErrorCode_InexistentFile); + } + } + + ~FileRabi() + { + if (fp_) + fclose(fp_); + } + }; + } + + + struct PngReader::PngRabi + { + png_structp png_; + png_infop info_; + png_infop endInfo_; + + void Destruct() + { + if (png_) + { + png_destroy_read_struct(&png_, &info_, &endInfo_); + + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + } + } + + PngRabi() + { + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + info_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, NULL, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + endInfo_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, &info_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + ~PngRabi() + { + Destruct(); + } + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size); + }; + + + void PngReader::CheckHeader(const void* header) + { + int is_png = !png_sig_cmp((png_bytep) header, 0, 8); + if (!is_png) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + PngReader::PngReader() + { + width_ = 0; + height_ = 0; + pitch_ = 0; + format_ = PixelFormat_Grayscale8; + } + + void PngReader::Read(PngRabi& rabi) + { + png_set_sig_bytes(rabi.png_, 8); + + png_read_info(rabi.png_, rabi.info_); + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + int compression_type, filter_method; + // get size and bit-depth of the PNG-image + png_get_IHDR(rabi.png_, rabi.info_, + &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_method); + + width_ = width; + height_ = height; + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) + { + format_ = PixelFormat_Grayscale8; + pitch_ = width_; + } + else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) + { + format_ = PixelFormat_Grayscale16; + pitch_ = 2 * width_; + + if (Toolbox::DetectEndianness() == Endianness_Little) + { + png_set_swap(rabi.png_); + } + } + else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) + { + format_ = PixelFormat_Grayscale8; + pitch_ = 3 * width_; + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + + buffer_.resize(height_ * pitch_); + + if (height_ == 0 || width_ == 0) + { + // Empty image, we are done + return; + } + + png_read_update_info(rabi.png_, rabi.info_); + + std::vector<png_bytep> rows(height_); + for (size_t i = 0; i < height_; i++) + { + rows[i] = &buffer_[0] + i * pitch_; + } + + png_read_image(rabi.png_, &rows[0]); + } + + void PngReader::ReadFromFile(const char* filename) + { + FileRabi f(filename); + + char header[8]; + if (fread(header, 1, 8, f.fp_) != 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(header); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + png_init_io(rabi.png_, f.fp_); + + Read(rabi); + } + + + + namespace + { + struct MemoryBuffer + { + const uint8_t* buffer_; + size_t size_; + size_t pos_; + bool ok_; + }; + } + + + void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, + png_bytep outBytes, + png_size_t byteCountToRead) + { + MemoryBuffer* from = (MemoryBuffer*) png_get_io_ptr(png_ptr); + + if (!from->ok_) + { + return; + } + + if (from->pos_ + byteCountToRead > from->size_) + { + from->ok_ = false; + return; + } + + memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); + + from->pos_ += byteCountToRead; + } + + + void PngReader::ReadFromMemory(const void* buffer, + size_t size) + { + if (size < 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(buffer); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + MemoryBuffer tmp; + tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header + tmp.size_ = size - 8; + tmp.pos_ = 0; + tmp.ok_ = true; + + png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); + + Read(rabi); + + if (!tmp.ok_) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + void PngReader::ReadFromMemory(const std::string& buffer) + { + if (buffer.size() != 0) + ReadFromMemory(&buffer[0], buffer.size()); + else + ReadFromMemory(NULL, 0); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngReader.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,104 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <vector> +#include <stdint.h> +#include <boost/shared_ptr.hpp> + +namespace Orthanc +{ + class PngReader + { + private: + struct PngRabi; + + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + std::vector<uint8_t> buffer_; + + void CheckHeader(const void* header); + + void Read(PngRabi& rabi); + + public: + PngReader(); + + PixelFormat GetFormat() const + { + return format_; + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetPitch() const + { + return pitch_; + } + + const void* GetBuffer() const + { + if (buffer_.size() > 0) + return &buffer_[0]; + else + return NULL; + } + + const void* GetBuffer(unsigned int y) const + { + if (buffer_.size() > 0) + return &buffer_[y * pitch_]; + else + return NULL; + } + + void ReadFromFile(const char* filename); + + void ReadFromMemory(const void* buffer, + size_t size); + + void ReadFromMemory(const std::string& buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngWriter.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,260 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PngWriter.h" + +#include <vector> +#include <stdint.h> +#include <png.h> +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" +#include "../Toolbox.h" + + +// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 +// http://zarb.org/~gc/html/libpng.html +/* + void write_row_callback(png_ptr, png_uint_32 row, int pass) + { + }*/ + + + + +/* bool isError_; + +// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 + +static void ErrorHandler(png_structp png, png_const_charp message) +{ +printf("** [%s]\n", message); + +PngWriter* that = (PngWriter*) png_get_error_ptr(png); +that->isError_ = true; +printf("** %d\n", (int)that); + +//((PngWriter*) payload)->isError_ = true; +} + +static void WarningHandler(png_structp png, png_const_charp message) +{ + printf("++ %d\n", (int)message); +}*/ + + +namespace Orthanc +{ + struct PngWriter::PImpl + { + png_structp png_; + png_infop info_; + + // Filled by Prepare() + std::vector<uint8_t*> rows_; + int bitDepth_; + int colorType_; + }; + + + + PngWriter::PngWriter() : pimpl_(new PImpl) + { + pimpl_->png_ = NULL; + pimpl_->info_ = NULL; + + pimpl_->png_ = png_create_write_struct + (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); + if (!pimpl_->png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + pimpl_->info_ = png_create_info_struct(pimpl_->png_); + if (!pimpl_->info_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + PngWriter::~PngWriter() + { + if (pimpl_->info_) + { + png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); + } + + if (pimpl_->png_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + } + } + + + + void PngWriter::Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + pimpl_->rows_.resize(height); + for (unsigned int y = 0; y < height; y++) + { + pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; + } + + switch (format) + { + case PixelFormat_RGB24: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; + break; + + case PixelFormat_Grayscale8: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + pimpl_->bitDepth_ = 16; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void PngWriter::Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format) + { + png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, + pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(pimpl_->png_, pimpl_->info_); + + if (height > 0) + { + switch (format) + { + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); + + if (Toolbox::DetectEndianness() == Endianness_Little) + { + // Must swap the endianness!! + png_write_png(pimpl_->png_, pimpl_->info_, PNG_TRANSFORM_SWAP_ENDIAN, NULL); + } + + break; + + default: + png_write_image(pimpl_->png_, &pimpl_->rows_[0]); + } + } + + png_write_end(pimpl_->png_, NULL); + } + + + void PngWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + Prepare(width, height, pitch, format, buffer); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + png_init_io(pimpl_->png_, fp); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_CannotWriteFile); + } + + Compress(width, height, pitch, format); + + fclose(fp); + } + + + + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size) + { + ChunkedBuffer* buffer = (ChunkedBuffer*) png_get_io_ptr(png_ptr); + buffer->AddChunk(reinterpret_cast<const char*>(data), size); + } + + + + void PngWriter::WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + ChunkedBuffer chunks; + + Prepare(width, height, pitch, format, buffer); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_InternalError); + } + + png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); + + Compress(width, height, pitch, format); + + chunks.Flatten(png); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileFormats/PngWriter.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <boost/shared_ptr.hpp> +#include <string> + +namespace Orthanc +{ + class PngWriter + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + void Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format); + + void Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + public: + PngWriter(); + + ~PngWriter(); + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + }; +}
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/CompressedFileStorageAccessor.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileInfo.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/FileInfo.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorage.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/FileStorage.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorage.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/FileStorage.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorageAccessor.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/FileStorageAccessor.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorageAccessor.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/FileStorageAccessor.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/StorageAccessor.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/FileStorage/StorageAccessor.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpClient.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,262 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "HttpClient.h" + +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" + +#include <string.h> +#include <curl/curl.h> + + +namespace Orthanc +{ + struct HttpClient::PImpl + { + CURL* curl_; + struct curl_slist *postHeaders_; + }; + + + static CURLcode CheckCode(CURLcode code) + { + if (code != CURLE_OK) + { + throw OrthancException("libCURL error: " + std::string(curl_easy_strerror(code))); + } + + return code; + } + + + static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *payload) + { + std::string& target = *(static_cast<std::string*>(payload)); + + size_t length = size * nmemb; + if (length == 0) + return 0; + + size_t pos = target.size(); + + target.resize(pos + length); + memcpy(&target.at(pos), buffer, length); + + return length; + } + + + void HttpClient::Setup() + { + pimpl_->postHeaders_ = NULL; + if ((pimpl_->postHeaders_ = curl_slist_append(pimpl_->postHeaders_, "Expect:")) == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + pimpl_->curl_ = curl_easy_init(); + if (!pimpl_->curl_) + { + curl_slist_free_all(pimpl_->postHeaders_); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlCallback)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); + +#if ORTHANC_SSL_ENABLED == 1 + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); +#endif + + // This fixes the "longjmp causes uninitialized stack frame" crash + // that happens on modern Linux versions. + // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1)); + + url_ = ""; + method_ = HttpMethod_Get; + lastStatus_ = HttpStatus_200_Ok; + isVerbose_ = false; + } + + + HttpClient::HttpClient() : pimpl_(new PImpl) + { + Setup(); + } + + + HttpClient::HttpClient(const HttpClient& other) : pimpl_(new PImpl) + { + Setup(); + + if (other.IsVerbose()) + { + SetVerbose(true); + } + + if (other.credentials_.size() != 0) + { + credentials_ = other.credentials_; + } + } + + + HttpClient::~HttpClient() + { + curl_easy_cleanup(pimpl_->curl_); + curl_slist_free_all(pimpl_->postHeaders_); + } + + + void HttpClient::SetVerbose(bool isVerbose) + { + isVerbose_ = isVerbose; + + if (isVerbose_) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1)); + } + else + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0)); + } + } + + + bool HttpClient::Apply(std::string& answer) + { + answer.clear(); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL)); + + if (credentials_.size() != 0) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str())); + } + + switch (method_) + { + case HttpMethod_Get: + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L)); + break; + + case HttpMethod_Post: + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); + + if (postData_.size() > 0) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, postData_.size())); + } + else + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); + } + + break; + + case HttpMethod_Delete: + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); + break; + + case HttpMethod_Put: + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + // Do the actual request + CheckCode(curl_easy_perform(pimpl_->curl_)); + + long status; + CheckCode(curl_easy_getinfo(pimpl_->curl_, CURLINFO_RESPONSE_CODE, &status)); + + if (status == 0) + { + // This corresponds to a call to an inexistent host + lastStatus_ = HttpStatus_500_InternalServerError; + } + else + { + lastStatus_ = static_cast<HttpStatus>(status); + } + + return (status >= 200 && status < 300); + } + + + bool HttpClient::Apply(Json::Value& answer) + { + std::string s; + if (Apply(s)) + { + Json::Reader reader; + return reader.parse(s, answer); + } + else + { + return false; + } + } + + + void HttpClient::SetCredentials(const char* username, + const char* password) + { + credentials_ = std::string(username) + ":" + std::string(password); + } + + + void HttpClient::GlobalInitialize() + { + CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT)); + } + + void HttpClient::GlobalFinalize() + { + curl_global_cleanup(); + } + + const char* HttpClient::GetLastStatusText() const + { + return EnumerationToString(lastStatus_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpClient.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,127 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/Enumerations.h" + +#include <string> +#include <boost/shared_ptr.hpp> +#include <json/json.h> + +namespace Orthanc +{ + class HttpClient + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + std::string url_; + std::string credentials_; + HttpMethod method_; + HttpStatus lastStatus_; + std::string postData_; + bool isVerbose_; + + void Setup(); + + void operator= (const HttpClient&); // Forbidden + + public: + HttpClient(const HttpClient& base); + + HttpClient(); + + ~HttpClient(); + + void SetUrl(const char* url) + { + url_ = std::string(url); + } + + void SetUrl(const std::string& url) + { + url_ = url; + } + + const std::string& GetUrl() const + { + return url_; + } + + void SetMethod(HttpMethod method) + { + method_ = method; + } + + HttpMethod GetMethod() const + { + return method_; + } + + std::string& AccessPostData() + { + return postData_; + } + + const std::string& AccessPostData() const + { + return postData_; + } + + void SetVerbose(bool isVerbose); + + bool IsVerbose() const + { + return isVerbose_; + } + + bool Apply(std::string& answer); + + bool Apply(Json::Value& answer); + + HttpStatus GetLastStatus() const + { + return lastStatus_; + } + + const char* GetLastStatusText() const; + + void SetCredentials(const char* username, + const char* password); + + static void GlobalInitialize(); + + static void GlobalFinalize(); + }; +}
--- a/Core/HttpServer/BufferHttpSender.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/BufferHttpSender.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -58,13 +58,13 @@ void EmbeddedResourceHttpHandler::Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments, const std::string&) { - if (method != Orthanc_HttpMethod_Get) + if (method != HttpMethod_Get) { output.SendMethodNotAllowedError("GET"); return; @@ -82,7 +82,7 @@ catch (OrthancException& e) { LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath; - output.SendHeader(Orthanc_HttpStatus_404_NotFound); + output.SendHeader(HttpStatus_404_NotFound); } } }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -54,7 +54,7 @@ virtual void Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments,
--- a/Core/HttpServer/FilesystemHttpHandler.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -127,13 +127,13 @@ void FilesystemHttpHandler::Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments, const std::string&) { - if (method != Orthanc_HttpMethod_Get) + if (method != HttpMethod_Get) { output.SendMethodNotAllowedError("GET"); return; @@ -161,7 +161,7 @@ } else { - output.SendHeader(Orthanc_HttpStatus_404_NotFound); + output.SendHeader(HttpStatus_404_NotFound); } } }
--- a/Core/HttpServer/FilesystemHttpHandler.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -56,7 +56,7 @@ virtual void Handle( HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& arguments,
--- a/Core/HttpServer/FilesystemHttpSender.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpSender.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/FilesystemHttpSender.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpFileSender.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/HttpFileSender.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -47,7 +47,7 @@ if (!SendData(output)) { - output.SendHeader(Orthanc_HttpStatus_500_InternalServerError); + output.SendHeader(HttpStatus_500_InternalServerError); } } }
--- a/Core/HttpServer/HttpFileSender.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/HttpFileSender.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/HttpHandler.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/HttpHandler.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -36,7 +36,6 @@ #include <vector> #include <stdint.h> #include "../Toolbox.h" -#include "../../OrthancCppClient/HttpEnumerations.h" namespace Orthanc { @@ -54,7 +53,7 @@ virtual bool IsServedUri(const UriComponents& uri) = 0; virtual void Handle(HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& getArguments,
--- a/Core/HttpServer/HttpOutput.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/HttpOutput.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -38,7 +38,6 @@ #include <boost/lexical_cast.hpp> #include "../OrthancException.h" #include "../Toolbox.h" -#include "../../OrthancCppClient/HttpException.h" namespace Orthanc { @@ -104,17 +103,17 @@ void HttpOutput::SendMethodNotAllowedError(const std::string& allowed) { std::string s = - "HTTP/1.1 405 " + std::string(HttpException::GetDescription(Orthanc_HttpStatus_405_MethodNotAllowed)) + + "HTTP/1.1 405 " + std::string(EnumerationToString(HttpStatus_405_MethodNotAllowed)) + "\r\nAllow: " + allowed + "\r\n\r\n"; Send(&s[0], s.size()); } - void HttpOutput::SendHeader(Orthanc_HttpStatus status) + void HttpOutput::SendHeader(HttpStatus status) { - if (status == Orthanc_HttpStatus_200_Ok || - status == Orthanc_HttpStatus_405_MethodNotAllowed) + if (status == HttpStatus_200_Ok || + status == HttpStatus_405_MethodNotAllowed) { throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput"); } @@ -123,11 +122,11 @@ } - void HttpOutput::SendHeaderInternal(Orthanc_HttpStatus status) + void HttpOutput::SendHeaderInternal(HttpStatus status) { std::string s = "HTTP/1.1 " + boost::lexical_cast<std::string>(status) + - " " + std::string(HttpException::GetDescription(status)) + + " " + std::string(EnumerationToString(status)) + "\r\n\r\n"; Send(&s[0], s.size()); } @@ -190,7 +189,7 @@ void HttpOutput::Redirect(const std::string& path) { std::string s = - "HTTP/1.1 301 " + std::string(HttpException::GetDescription(Orthanc_HttpStatus_301_MovedPermanently)) + + "HTTP/1.1 301 " + std::string(EnumerationToString(HttpStatus_301_MovedPermanently)) + "\r\nLocation: " + path + "\r\n\r\n"; Send(&s[0], s.size());
--- a/Core/HttpServer/HttpOutput.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/HttpOutput.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -45,7 +45,7 @@ private: typedef std::list< std::pair<std::string, std::string> > Header; - void SendHeaderInternal(Orthanc_HttpStatus status); + void SendHeaderInternal(HttpStatus status); void PrepareOkHeader(Header& header, const char* contentType, @@ -74,7 +74,7 @@ void SendMethodNotAllowedError(const std::string& allowed); - void SendHeader(Orthanc_HttpStatus status); + void SendHeader(HttpStatus status); void Redirect(const std::string& path);
--- a/Core/HttpServer/MongooseServer.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -268,9 +268,9 @@ - static PostDataStatus ReadPostData(std::string& postData, - struct mg_connection *connection, - const HttpHandler::Arguments& headers) + static PostDataStatus ReadBody(std::string& postData, + struct mg_connection *connection, + const HttpHandler::Arguments& headers) { HttpHandler::Arguments::const_iterator cs = headers.find("content-length"); if (cs == headers.end()) @@ -303,6 +303,7 @@ { return PostDataStatus_Failure; } + assert(r <= length); length -= r; pos += r; @@ -322,7 +323,7 @@ std::string boundary = "--" + contentType.substr(multipartLength); std::string postData; - PostDataStatus status = ReadPostData(postData, connection, headers); + PostDataStatus status = ReadBody(postData, connection, headers); if (status != PostDataStatus_Success) { @@ -454,6 +455,114 @@ } + static std::string GetAuthenticatedUsername(const HttpHandler::Arguments& headers) + { + HttpHandler::Arguments::const_iterator auth = headers.find("authorization"); + + if (auth == headers.end()) + { + return ""; + } + + std::string s = auth->second; + if (s.substr(0, 6) != "Basic ") + { + return ""; + } + + std::string b64 = s.substr(6); + std::string decoded = Toolbox::DecodeBase64(b64); + size_t semicolons = decoded.find(':'); + + if (semicolons == std::string::npos) + { + // Bad-formatted request + return ""; + } + else + { + return decoded.substr(0, semicolons); + } + } + + + static bool ExtractMethod(HttpMethod& method, + const struct mg_request_info *request, + const HttpHandler::Arguments& headers, + const HttpHandler::Arguments& argumentsGET) + { + std::string overriden; + + // Check whether some PUT/DELETE faking is done + + // 1. Faking with Google's approach + HttpHandler::Arguments::const_iterator methodOverride = + headers.find("x-http-method-override"); + + if (methodOverride != headers.end()) + { + overriden = methodOverride->second; + } + else if (!strcmp(request->request_method, "GET")) + { + // 2. Faking with Ruby on Rail's approach + // GET /my/resource?_method=delete <=> DELETE /my/resource + methodOverride = argumentsGET.find("_method"); + if (methodOverride != argumentsGET.end()) + { + overriden = methodOverride->second; + } + } + + if (overriden.size() > 0) + { + // A faking has been done within this request + Toolbox::ToUpperCase(overriden); + + LOG(INFO) << "HTTP method faking has been detected for " << overriden; + + if (overriden == "PUT") + { + method = HttpMethod_Put; + return true; + } + else if (overriden == "DELETE") + { + method = HttpMethod_Delete; + return true; + } + else + { + return false; + } + } + + // No PUT/DELETE faking was present + if (!strcmp(request->request_method, "GET")) + { + method = HttpMethod_Get; + } + else if (!strcmp(request->request_method, "POST")) + { + method = HttpMethod_Post; + } + else if (!strcmp(request->request_method, "DELETE")) + { + method = HttpMethod_Delete; + } + else if (!strcmp(request->request_method, "PUT")) + { + method = HttpMethod_Put; + } + else + { + return false; + } + + return true; + } + + static void* Callback(enum mg_event event, struct mg_connection *connection, @@ -464,30 +573,7 @@ MongooseServer* that = (MongooseServer*) (request->user_data); MongooseOutput output(connection); - // Compute the method - Orthanc_HttpMethod method; - if (!strcmp(request->request_method, "GET")) - { - method = Orthanc_HttpMethod_Get; - } - else if (!strcmp(request->request_method, "POST")) - { - method = Orthanc_HttpMethod_Post; - } - else if (!strcmp(request->request_method, "DELETE")) - { - method = Orthanc_HttpMethod_Delete; - } - else if (!strcmp(request->request_method, "PUT")) - { - method = Orthanc_HttpMethod_Put; - } - else - { - output.SendHeader(Orthanc_HttpStatus_405_MethodNotAllowed); - return (void*) ""; - } - + // Check remote calls if (!that->IsRemoteAccessAllowed() && request->remote_ip != LOCALHOST) { @@ -495,8 +581,9 @@ return (void*) ""; } - HttpHandler::Arguments arguments, headers; + // Extract the HTTP headers + HttpHandler::Arguments headers; for (int i = 0; i < request->num_headers; i++) { std::string name = request->http_headers[i].name; @@ -504,6 +591,24 @@ headers.insert(std::make_pair(name, request->http_headers[i].value)); } + + // Extract the GET arguments + HttpHandler::Arguments argumentsGET; + if (!strcmp(request->request_method, "GET")) + { + HttpHandler::ParseGetQuery(argumentsGET, request->query_string); + } + + + // Compute the HTTP method, taking method faking into consideration + HttpMethod method; + if (!ExtractMethod(method, request, headers, argumentsGET)) + { + output.SendHeader(HttpStatus_400_BadRequest); + return (void*) ""; + } + + // Authenticate this connection if (that->IsAuthenticationEnabled() && !Authorize(*that, headers, output)) @@ -511,83 +616,115 @@ return (void*) ""; } - std::string postData; - if (method == Orthanc_HttpMethod_Get) + // Apply the filter, if it is installed + const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter(); + if (filter != NULL) { - HttpHandler::ParseGetQuery(arguments, request->query_string); + std::string username = GetAuthenticatedUsername(headers); + + char remoteIp[24]; + sprintf(remoteIp, "%d.%d.%d.%d", + reinterpret_cast<const uint8_t*>(&request->remote_ip) [3], + reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], + reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], + reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]); + + if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str())) + { + SendUnauthorized(output); + return (void*) ""; + } } - else if (method == Orthanc_HttpMethod_Post || - method == Orthanc_HttpMethod_Put) + + + // Extract the body of the request for PUT and POST + std::string body; + if (method == HttpMethod_Post || + method == HttpMethod_Put) { + PostDataStatus status; + HttpHandler::Arguments::const_iterator ct = headers.find("content-type"); if (ct == headers.end()) { - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); - return (void*) ""; - } - - PostDataStatus status; - - std::string contentType = ct->second; - if (contentType.size() >= multipartLength && - !memcmp(contentType.c_str(), multipart, multipartLength)) - { - status = ParseMultipartPost(postData, connection, headers, contentType, that->GetChunkStore()); + // No content-type specified. Assume no multi-part content occurs at this point. + status = ReadBody(body, connection, headers); } else { - status = ReadPostData(postData, connection, headers); + std::string contentType = ct->second; + if (contentType.size() >= multipartLength && + !memcmp(contentType.c_str(), multipart, multipartLength)) + { + status = ParseMultipartPost(body, connection, headers, contentType, that->GetChunkStore()); + } + else + { + status = ReadBody(body, connection, headers); + } } switch (status) { - case PostDataStatus_NoLength: - output.SendHeader(Orthanc_HttpStatus_411_LengthRequired); - return (void*) ""; + case PostDataStatus_NoLength: + output.SendHeader(HttpStatus_411_LengthRequired); + return (void*) ""; - case PostDataStatus_Failure: - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); - return (void*) ""; + case PostDataStatus_Failure: + output.SendHeader(HttpStatus_400_BadRequest); + return (void*) ""; - case PostDataStatus_Pending: - output.AnswerBufferWithContentType(NULL, 0, ""); - return (void*) ""; + case PostDataStatus_Pending: + output.AnswerBufferWithContentType(NULL, 0, ""); + return (void*) ""; - default: - break; + default: + break; } } + + // Call the proper handler for this URI UriComponents uri; - Toolbox::SplitUriComponents(uri, request->uri); + try + { + Toolbox::SplitUriComponents(uri, request->uri); + } + catch (OrthancException) + { + output.SendHeader(HttpStatus_400_BadRequest); + return (void*) ""; + } + HttpHandler* handler = that->FindHandler(uri); if (handler) { try { - handler->Handle(output, method, uri, headers, arguments, postData); + LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); + handler->Handle(output, method, uri, headers, argumentsGET, body); } catch (OrthancException& e) { LOG(ERROR) << "MongooseServer Exception [" << e.What() << "]"; - output.SendHeader(Orthanc_HttpStatus_500_InternalServerError); + output.SendHeader(HttpStatus_500_InternalServerError); } catch (boost::bad_lexical_cast&) { LOG(ERROR) << "MongooseServer Exception: Bad lexical cast"; - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); + output.SendHeader(HttpStatus_400_BadRequest); } catch (std::runtime_error&) { LOG(ERROR) << "MongooseServer Exception: Presumably a bad JSON request"; - output.SendHeader(Orthanc_HttpStatus_400_BadRequest); + output.SendHeader(HttpStatus_400_BadRequest); } } else { - output.SendHeader(Orthanc_HttpStatus_404_NotFound); + output.SendHeader(HttpStatus_404_NotFound); } // Mark as processed @@ -613,6 +750,7 @@ authentication_ = false; ssl_ = false; port_ = 8000; + filter_ = NULL; } @@ -737,6 +875,11 @@ remoteAllowed_ = allowed; } + void MongooseServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter) + { + Stop(); + filter_ = &filter; + } bool MongooseServer::IsValidBasicHttpAuthentication(const std::string& basic) const {
--- a/Core/HttpServer/MongooseServer.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/HttpServer/MongooseServer.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -44,6 +44,19 @@ { class ChunkStore; + class IIncomingHttpRequestFilter + { + public: + virtual ~IIncomingHttpRequestFilter() + { + } + + virtual bool IsAllowed(HttpMethod method, + const char* uri, + const char* ip, + const char* username) const = 0; + }; + class MongooseServer { private: @@ -62,6 +75,7 @@ bool ssl_; std::string certificate_; uint16_t port_; + IIncomingHttpRequestFilter* filter_; bool IsRunning() const; @@ -116,6 +130,13 @@ void SetRemoteAccessAllowed(bool allowed); + const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const + { + return filter_; + } + + void SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter); + void ClearHandlers(); // Can return NULL if no handler is associated to this URI
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ICommand.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,48 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IDynamicObject.h" + +namespace Orthanc +{ + /** + * This class is the base class for the "Command" design pattern. + * http://en.wikipedia.org/wiki/Command_pattern + **/ + class ICommand : public IDynamicObject + { + public: + virtual bool Execute() = 0; + }; +}
--- a/Core/IDynamicObject.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/IDynamicObject.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Lua/LuaContext.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,150 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "LuaContext.h" + +#include <glog/logging.h> + +extern "C" +{ +#include <lualib.h> +#include <lauxlib.h> +} + +namespace Orthanc +{ + int LuaContext::PrintToLog(lua_State *state) + { + // Get the pointer to the "LuaContext" underlying object + lua_getglobal(state, "_LuaContext"); + assert(lua_type(state, -1) == LUA_TLIGHTUSERDATA); + LuaContext* that = const_cast<LuaContext*>(reinterpret_cast<const LuaContext*>(lua_topointer(state, -1))); + assert(that != NULL); + lua_pop(state, 1); + + // http://medek.wordpress.com/2009/02/03/wrapping-lua-errors-and-print-function/ + int nArgs = lua_gettop(state); + lua_getglobal(state, "tostring"); + + // Make sure you start at 1 *NOT* 0 for arrays in Lua. + std::string result; + + for (int i = 1; i <= nArgs; i++) + { + const char *s; + lua_pushvalue(state, -1); + lua_pushvalue(state, i); + lua_call(state, 1, 1); + s = lua_tostring(state, -1); + + if (result.size() > 0) + result.append(", "); + + if (s == NULL) + result.append("<No conversion to string>"); + else + result.append(s); + + lua_pop(state, 1); + } + + LOG(INFO) << "Lua says: " << result; + that->log_.append(result); + that->log_.append("\n"); + + return 0; + } + + + LuaContext::LuaContext() + { + lua_ = luaL_newstate(); + if (!lua_) + { + throw LuaException("Unable to create the Lua context"); + } + + luaL_openlibs(lua_); + lua_register(lua_, "print", PrintToLog); + + lua_pushlightuserdata(lua_, this); + lua_setglobal(lua_, "_LuaContext"); + } + + + LuaContext::~LuaContext() + { + lua_close(lua_); + } + + + void LuaContext::Execute(std::string* output, + const std::string& command) + { + boost::mutex::scoped_lock lock(mutex_); + + log_.clear(); + int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") || + lua_pcall(lua_, 0, 0, 0)); + + if (error) + { + assert(lua_gettop(lua_) >= 1); + + std::string description(lua_tostring(lua_, -1)); + lua_pop(lua_, 1); /* pop error message from the stack */ + throw LuaException(description); + } + + if (output != NULL) + { + *output = log_; + } + } + + + void LuaContext::Execute(EmbeddedResources::FileResourceId resource) + { + std::string command; + EmbeddedResources::GetFileResource(command, resource); + Execute(command); + } + + + bool LuaContext::IsExistingFunction(const char* name) + { + boost::mutex::scoped_lock lock(mutex_); + lua_settop(lua_, 0); + lua_getglobal(lua_, name); + return lua_type(lua_, -1) == LUA_TFUNCTION; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Lua/LuaContext.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "LuaException.h" + +#include <boost/thread.hpp> + +extern "C" +{ +#include <lua.h> +} + +#include <EmbeddedResources.h> + + +namespace Orthanc +{ + class LuaContext : public boost::noncopyable + { + private: + friend class LuaFunctionCall; + + lua_State *lua_; + boost::mutex mutex_; + std::string log_; + + static int PrintToLog(lua_State *L); + + void Execute(std::string* output, + const std::string& command); + + public: + LuaContext(); + + ~LuaContext(); + + void Execute(const std::string& command) + { + Execute(NULL, command); + } + + void Execute(std::string& output, + const std::string& command) + { + Execute(&output, command); + } + + void Execute(EmbeddedResources::FileResourceId resource); + + bool IsExistingFunction(const char* name); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Lua/LuaException.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,52 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../OrthancException.h" + +namespace Orthanc +{ + class LuaException : public OrthancException + { + public: + LuaException(const char* explanation) : + OrthancException(explanation) + { + } + + LuaException(const std::string& explanation) : + OrthancException(explanation) + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Lua/LuaFunctionCall.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,192 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "LuaFunctionCall.h" + + +namespace Orthanc +{ + void LuaFunctionCall::CheckAlreadyExecuted() + { + if (isExecuted_) + { + throw LuaException("Arguments cannot be pushed after the function is executed"); + } + } + + LuaFunctionCall::LuaFunctionCall(LuaContext& context, + const char* functionName) : + context_(context), + lock_(context.mutex_), + isExecuted_(false) + { + // Clear the stack to fulfill the invariant + lua_settop(context_.lua_, 0); + lua_getglobal(context_.lua_, functionName); + } + + void LuaFunctionCall::PushString(const std::string& value) + { + CheckAlreadyExecuted(); + lua_pushstring(context_.lua_, value.c_str()); + } + + void LuaFunctionCall::PushBoolean(bool value) + { + CheckAlreadyExecuted(); + lua_pushboolean(context_.lua_, value); + } + + void LuaFunctionCall::PushInteger(int value) + { + CheckAlreadyExecuted(); + lua_pushinteger(context_.lua_, value); + } + + void LuaFunctionCall::PushDouble(double value) + { + CheckAlreadyExecuted(); + lua_pushnumber(context_.lua_, value); + } + + void LuaFunctionCall::PushJSON(const Json::Value& value) + { + CheckAlreadyExecuted(); + + if (value.isString()) + { + lua_pushstring(context_.lua_, value.asCString()); + } + else if (value.isDouble()) + { + lua_pushnumber(context_.lua_, value.asDouble()); + } + else if (value.isInt()) + { + lua_pushinteger(context_.lua_, value.asInt()); + } + else if (value.isUInt()) + { + lua_pushinteger(context_.lua_, value.asUInt()); + } + else if (value.isBool()) + { + lua_pushboolean(context_.lua_, value.asBool()); + } + else if (value.isNull()) + { + lua_pushnil(context_.lua_); + } + else if (value.isArray()) + { + lua_newtable(context_.lua_); + + // http://lua-users.org/wiki/SimpleLuaApiExample + for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) + { + // Push the table index (note the "+1" because of Lua conventions) + lua_pushnumber(context_.lua_, i + 1); + + // Push the value of the cell + PushJSON(value[i]); + + // Stores the pair in the table + lua_rawset(context_.lua_, -3); + } + } + else if (value.isObject()) + { + lua_newtable(context_.lua_); + + Json::Value::Members members = value.getMemberNames(); + + for (Json::Value::Members::const_iterator + it = members.begin(); it != members.end(); it++) + { + // Push the index of the cell + lua_pushstring(context_.lua_, it->c_str()); + + // Push the value of the cell + PushJSON(value[*it]); + + // Stores the pair in the table + lua_rawset(context_.lua_, -3); + } + } + else + { + throw LuaException("Unsupported JSON conversion"); + } + } + + void LuaFunctionCall::Execute(int numOutputs) + { + CheckAlreadyExecuted(); + + assert(lua_gettop(context_.lua_) >= 1); + int nargs = lua_gettop(context_.lua_) - 1; + int error = lua_pcall(context_.lua_, nargs, numOutputs, 0); + + if (error) + { + assert(lua_gettop(context_.lua_) >= 1); + + std::string description(lua_tostring(context_.lua_, -1)); + lua_pop(context_.lua_, 1); /* pop error message from the stack */ + throw LuaException(description); + } + + if (lua_gettop(context_.lua_) < numOutputs) + { + throw LuaException("The function does not give the expected number of outputs"); + } + + isExecuted_ = true; + } + + bool LuaFunctionCall::ExecutePredicate() + { + Execute(1); + + if (lua_gettop(context_.lua_) == 0) + { + throw LuaException("No output was provided by the function"); + } + + if (!lua_isboolean(context_.lua_, 1)) + { + throw LuaException("The function is not a predicate (only true/false outputs allowed)"); + } + + return lua_toboolean(context_.lua_, 1) != 0; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Lua/LuaFunctionCall.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "LuaContext.h" + +#include <json/json.h> + + +namespace Orthanc +{ + class LuaFunctionCall : public boost::noncopyable + { + private: + LuaContext& context_; + boost::mutex::scoped_lock lock_; + bool isExecuted_; + + void CheckAlreadyExecuted(); + + public: + LuaFunctionCall(LuaContext& context, + const char* functionName); + + void PushString(const std::string& value); + + void PushBoolean(bool value); + + void PushInteger(int value); + + void PushDouble(double value); + + void PushJSON(const Json::Value& value); + + void Execute(int numOutputs = 0); + + bool ExecutePredicate(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ArrayFilledByThreads.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,121 @@ +#include "ArrayFilledByThreads.h" + +#include "../MultiThreading/ThreadedCommandProcessor.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + class ArrayFilledByThreads::Command : public ICommand + { + private: + ArrayFilledByThreads& that_; + size_t index_; + + public: + Command(ArrayFilledByThreads& that, + size_t index) : + that_(that), + index_(index) + { + } + + virtual bool Execute() + { + std::auto_ptr<IDynamicObject> obj(that_.filler_.GetFillerItem(index_)); + if (obj.get() == NULL) + { + return false; + } + else + { + boost::mutex::scoped_lock lock(that_.mutex_); + that_.array_[index_] = obj.release(); + return true; + } + } + }; + + void ArrayFilledByThreads::Clear() + { + for (size_t i = 0; i < array_.size(); i++) + { + if (array_[i]) + delete array_[i]; + } + + array_.clear(); + filled_ = false; + } + + void ArrayFilledByThreads::Update() + { + if (!filled_) + { + array_.resize(filler_.GetFillerSize()); + + Orthanc::ThreadedCommandProcessor processor(threadCount_); + for (size_t i = 0; i < array_.size(); i++) + { + processor.Post(new Command(*this, i)); + } + + processor.Join(); + filled_ = true; + } + } + + + ArrayFilledByThreads::ArrayFilledByThreads(IFiller& filler) : filler_(filler) + { + filled_ = false; + threadCount_ = 4; + } + + + ArrayFilledByThreads::~ArrayFilledByThreads() + { + Clear(); + } + + + void ArrayFilledByThreads::Reload() + { + Clear(); + Update(); + } + + + void ArrayFilledByThreads::Invalidate() + { + Clear(); + } + + + void ArrayFilledByThreads::SetThreadCount(unsigned int t) + { + if (t < 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + threadCount_ = t; + } + + + size_t ArrayFilledByThreads::GetSize() + { + Update(); + return array_.size(); + } + + + IDynamicObject& ArrayFilledByThreads::GetItem(size_t index) + { + if (index >= GetSize()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + return *array_[index]; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ArrayFilledByThreads.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,54 @@ +#pragma once + +#include <boost/thread.hpp> + +#include "../ICommand.h" + +namespace Orthanc +{ + class ArrayFilledByThreads + { + public: + class IFiller + { + public: + virtual size_t GetFillerSize() = 0; + + virtual IDynamicObject* GetFillerItem(size_t index) = 0; + }; + + private: + IFiller& filler_; + boost::mutex mutex_; + std::vector<IDynamicObject*> array_; + bool filled_; + unsigned int threadCount_; + + class Command; + + void Clear(); + + void Update(); + + public: + ArrayFilledByThreads(IFiller& filler); + + ~ArrayFilledByThreads(); + + void Reload(); + + void Invalidate(); + + void SetThreadCount(unsigned int t); + + unsigned int GetThreadCount() const + { + return threadCount_; + } + + size_t GetSize(); + + IDynamicObject& GetItem(size_t index); + }; +} +
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -102,7 +102,11 @@ assert(t.get() != NULL); assert(bag->pimpl_->activeThreads_.find(r.get()) == bag->pimpl_->activeThreads_.end()); - t->join(); + if (t->joinable()) + { + t->join(); + } + bag->pimpl_->oneThreadIsJoined_.notify_one(); } @@ -128,7 +132,11 @@ // Stop the finish listener pimpl_->stopFinishListener_ = true; pimpl_->oneThreadIsStopped_.notify_one(); // Awakens the listener - pimpl_->finishListener_->join(); + + if (pimpl_->finishListener_->joinable()) + { + pimpl_->finishListener_->join(); + } }
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/MultiThreading/IRunnableBySteps.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/MultiThreading/IRunnableBySteps.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/SharedMessageQueue.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,122 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SharedMessageQueue.h" + +namespace Orthanc +{ + SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) + { + maxSize_ = maxSize; + } + + + SharedMessageQueue::~SharedMessageQueue() + { + for (Queue::iterator it = queue_.begin(); it != queue_.end(); it++) + { + delete *it; + } + } + + + void SharedMessageQueue::Enqueue(IDynamicObject* message) + { + boost::mutex::scoped_lock lock(mutex_); + + if (maxSize_ != 0 && queue_.size() > maxSize_) + { + // Too many elements in the queue: First remove the oldest + delete queue_.front(); + queue_.pop_front(); + } + + queue_.push_back(message); + elementAvailable_.notify_one(); + } + + + IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout) + { + boost::mutex::scoped_lock lock(mutex_); + + // Wait for a message to arrive in the FIFO queue + while (queue_.empty()) + { + if (millisecondsTimeout == 0) + { + elementAvailable_.wait(lock); + } + else + { + bool success = elementAvailable_.timed_wait + (lock, boost::posix_time::milliseconds(millisecondsTimeout)); + if (!success) + { + return NULL; + } + } + } + + std::auto_ptr<IDynamicObject> message(queue_.front()); + queue_.pop_front(); + emptied_.notify_all(); + + return message.release(); + } + + + + bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout) + { + boost::mutex::scoped_lock lock(mutex_); + + // Wait for the queue to become empty + if (!queue_.empty()) + { + if (millisecondsTimeout == 0) + { + emptied_.wait(lock); + } + else + { + if (!emptied_.timed_wait + (lock, boost::posix_time::milliseconds(millisecondsTimeout))) + { + return false; + } + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/SharedMessageQueue.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../IDynamicObject.h" + +#include <stdint.h> +#include <list> +#include <boost/thread.hpp> + +namespace Orthanc +{ + class SharedMessageQueue + { + private: + typedef std::list<IDynamicObject*> Queue; + + unsigned int maxSize_; + Queue queue_; + boost::mutex mutex_; + boost::condition_variable elementAvailable_; + boost::condition_variable emptied_; + + public: + SharedMessageQueue(unsigned int maxSize = 0); + + ~SharedMessageQueue(); + + // This transfers the ownership of the message + void Enqueue(IDynamicObject* message); + + // The caller is responsible to delete the dequeud message! + IDynamicObject* Dequeue(int32_t millisecondsTimeout); + + bool WaitEmpty(int32_t millisecondsTimeout); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,205 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ThreadedCommandProcessor.h" + +#include "../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) + { + 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/Core/MultiThreading/ThreadedCommandProcessor.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,94 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ICommand.h" + +#include "SharedMessageQueue.h" + +namespace Orthanc +{ + 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/Core/OrthancException.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/OrthancException.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -102,6 +102,9 @@ case ErrorCode_BadRequest: return "Bad request"; + case ErrorCode_NetworkProtocol: + return "Error in the network protocol"; + case ErrorCode_Custom: default: return "???";
--- a/Core/OrthancException.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/OrthancException.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -39,13 +39,19 @@ { class OrthancException { - private: + protected: ErrorCode error_; std::string custom_; public: static const char* GetDescription(ErrorCode error); + OrthancException(const char* custom) + { + error_ = ErrorCode_Custom; + custom_ = custom; + } + OrthancException(const std::string& custom) { error_ = ErrorCode_Custom;
--- a/Core/PngWriter.cpp Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,252 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PngWriter.h" - -#include <vector> -#include <stdint.h> -#include <png.h> -#include "OrthancException.h" -#include "ChunkedBuffer.h" - - -// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 -// http://zarb.org/~gc/html/libpng.html -/* - void write_row_callback(png_ptr, png_uint_32 row, int pass) - { - }*/ - - - - -/* bool isError_; - -// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 - -static void ErrorHandler(png_structp png, png_const_charp message) -{ -printf("** [%s]\n", message); - -PngWriter* that = (PngWriter*) png_get_error_ptr(png); -that->isError_ = true; -printf("** %d\n", (int)that); - -//((PngWriter*) payload)->isError_ = true; -} - -static void WarningHandler(png_structp png, png_const_charp message) -{ - printf("++ %d\n", (int)message); -}*/ - - -namespace Orthanc -{ - struct PngWriter::PImpl - { - png_structp png_; - png_infop info_; - - // Filled by Prepare() - std::vector<uint8_t*> rows_; - int bitDepth_; - int colorType_; - }; - - - - PngWriter::PngWriter() : pimpl_(new PImpl) - { - pimpl_->png_ = NULL; - pimpl_->info_ = NULL; - - pimpl_->png_ = png_create_write_struct - (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); - if (!pimpl_->png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - pimpl_->info_ = png_create_info_struct(pimpl_->png_); - if (!pimpl_->info_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - PngWriter::~PngWriter() - { - if (pimpl_->info_) - { - png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); - } - - if (pimpl_->png_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - } - } - - - - void PngWriter::Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - pimpl_->rows_.resize(height); - for (unsigned int y = 0; y < height; y++) - { - pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; - } - - switch (format) - { - case PixelFormat_RGB24: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; - break; - - case PixelFormat_Grayscale8: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - case PixelFormat_Grayscale16: - pimpl_->bitDepth_ = 16; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void PngWriter::Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format) - { - png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, - pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_write_info(pimpl_->png_, pimpl_->info_); - - if (height > 0) - { - switch (format) - { - case PixelFormat_Grayscale16: - // Must swap the endianness!! - png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); - png_write_png(pimpl_->png_, pimpl_->info_, PNG_TRANSFORM_SWAP_ENDIAN, NULL); - break; - - default: - png_write_image(pimpl_->png_, &pimpl_->rows_[0]); - } - } - - png_write_end(pimpl_->png_, NULL); - } - - - void PngWriter::WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - Prepare(width, height, pitch, format, buffer); - - FILE* fp = fopen(filename, "wb"); - if (!fp) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - png_init_io(pimpl_->png_, fp); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_CannotWriteFile); - } - - Compress(width, height, pitch, format); - - fclose(fp); - } - - - - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size) - { - ChunkedBuffer* buffer = (ChunkedBuffer*) png_get_io_ptr(png_ptr); - buffer->AddChunk(reinterpret_cast<const char*>(data), size); - } - - - - void PngWriter::WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - ChunkedBuffer chunks; - - Prepare(width, height, pitch, format, buffer); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_InternalError); - } - - png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); - - Compress(width, height, pitch, format); - - chunks.Flatten(png); - } -}
--- a/Core/PngWriter.h Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "Enumerations.h" - -#include <boost/shared_ptr.hpp> -#include <string> - -namespace Orthanc -{ - class PngWriter - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - void Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format); - - void Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - public: - PngWriter(); - - ~PngWriter(); - - void WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - }; -}
--- a/Core/RestApi/RestApi.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/RestApi/RestApi.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -180,7 +180,7 @@ } void RestApi::Handle(HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& getArguments, @@ -191,14 +191,14 @@ RestApiPath::Components components; UriComponents trailing; - if (method == Orthanc_HttpMethod_Get) + if (method == HttpMethod_Get) { for (GetHandlers::const_iterator it = getHandlers_.begin(); it != getHandlers_.end(); it++) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri); ok = true; GetCall call; call.output_ = &restOutput; @@ -213,14 +213,14 @@ } } } - else if (method == Orthanc_HttpMethod_Put) + else if (method == HttpMethod_Put) { for (PutHandlers::const_iterator it = putHandlers_.begin(); it != putHandlers_.end(); it++) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri); ok = true; PutCall call; call.output_ = &restOutput; @@ -235,14 +235,14 @@ } } } - else if (method == Orthanc_HttpMethod_Post) + else if (method == HttpMethod_Post) { for (PostHandlers::const_iterator it = postHandlers_.begin(); it != postHandlers_.end(); it++) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri); ok = true; PostCall call; call.output_ = &restOutput; @@ -257,14 +257,14 @@ } } } - else if (method == Orthanc_HttpMethod_Delete) + else if (method == HttpMethod_Delete) { for (DeleteHandlers::const_iterator it = deleteHandlers_.begin(); it != deleteHandlers_.end(); it++) { if (it->first->Match(components, trailing, uri)) { - LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri); + //LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri); ok = true; DeleteCall call; call.output_ = &restOutput; @@ -280,7 +280,8 @@ if (!ok) { - LOG(INFO) << "REST method " << method << " not allowed on: " << Toolbox::FlattenUri(uri); + LOG(INFO) << "REST method " << EnumerationToString(method) + << " not allowed on: " << Toolbox::FlattenUri(uri); output.SendMethodNotAllowedError(GetAcceptedMethods(uri)); } }
--- a/Core/RestApi/RestApi.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/RestApi/RestApi.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -212,7 +212,7 @@ virtual bool IsServedUri(const UriComponents& uri); virtual void Handle(HttpOutput& output, - Orthanc_HttpMethod method, + HttpMethod method, const UriComponents& uri, const Arguments& headers, const Arguments& getArguments,
--- a/Core/RestApi/RestApiOutput.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/RestApi/RestApiOutput.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -48,7 +48,7 @@ { if (!alreadySent_) { - output_.SendHeader(Orthanc_HttpStatus_400_BadRequest); + output_.SendHeader(HttpStatus_400_BadRequest); } } @@ -100,10 +100,10 @@ alreadySent_ = true; } - void RestApiOutput::SignalError(Orthanc_HttpStatus status) + void RestApiOutput::SignalError(HttpStatus status) { - if (status != Orthanc_HttpStatus_403_Forbidden && - status != Orthanc_HttpStatus_415_UnsupportedMediaType) + if (status != HttpStatus_403_Forbidden && + status != HttpStatus_415_UnsupportedMediaType) { throw OrthancException("This HTTP status is not allowed in a REST API"); }
--- a/Core/RestApi/RestApiOutput.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/RestApi/RestApiOutput.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -74,7 +74,7 @@ size_t length, const std::string& contentType); - void SignalError(Orthanc_HttpStatus status); + void SignalError(HttpStatus status); void Redirect(const std::string& path);
--- a/Core/RestApi/RestApiPath.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/RestApi/RestApiPath.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiPath.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/RestApi/RestApiPath.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/SQLite/Connection.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/Connection.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Connection.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/Connection.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/FunctionContext.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/FunctionContext.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/FunctionContext.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/FunctionContext.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/IScalarFunction.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/IScalarFunction.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/Statement.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/Statement.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Statement.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/Statement.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/StatementId.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/StatementId.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/StatementReference.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/StatementReference.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/Transaction.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/SQLite/Transaction.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/Toolbox.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Toolbox.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -34,6 +34,7 @@ #include "OrthancException.h" +#include <stdint.h> #include <string.h> #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> @@ -131,9 +132,9 @@ #if defined(_WIN32) static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType) { - // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx - finish = true; - return true; + // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx + finish = true; + return true; } #else static void SignalHandler(int) @@ -168,7 +169,7 @@ void Toolbox::ServerBarrier() { #if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, true); + SetConsoleCtrlHandler(ConsoleControlHandler, true); #else signal(SIGINT, SignalHandler); signal(SIGQUIT, SignalHandler); @@ -181,7 +182,7 @@ } #if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, false); + SetConsoleCtrlHandler(ConsoleControlHandler, false); #else signal(SIGINT, NULL); signal(SIGQUIT, NULL); @@ -207,10 +208,10 @@ const std::string& path) { boost::filesystem::ifstream f; - f.open(path, std::ifstream::in | std::ios::binary); + f.open(path, std::ifstream::in | std::ifstream::binary); if (!f.good()) { - throw OrthancException("Unable to open a file"); + throw OrthancException(ErrorCode_InexistentFile); } // http://www.cplusplus.com/reference/iostream/istream/tellg/ @@ -228,6 +229,26 @@ } + void Toolbox::WriteFile(const std::string& content, + const std::string& path) + { + boost::filesystem::ofstream f; + f.open(path, std::ofstream::binary); + if (!f.good()) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + if (content.size() != 0) + { + f.write(content.c_str(), content.size()); + } + + f.close(); + } + + + void Toolbox::RemoveFile(const std::string& path) { if (boost::filesystem::exists(path)) @@ -565,6 +586,33 @@ } } + bool Toolbox::IsSHA1(const std::string& str) + { + if (str.size() != 44) + { + return false; + } + + for (unsigned int i = 0; i < 44; i++) + { + if (i == 8 || + i == 17 || + i == 26 || + i == 35) + { + if (str[i] != '-') + return false; + } + else + { + if (!isalnum(str[i])) + return false; + } + } + + return true; + } + std::string Toolbox::GetNowIsoString() { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); @@ -644,4 +692,29 @@ s.resize(target); } + + + Endianness Toolbox::DetectEndianness() + { + // http://sourceforge.net/p/predef/wiki/Endianness/ + + uint8_t buffer[4]; + + buffer[0] = 0x00; + buffer[1] = 0x01; + buffer[2] = 0x02; + buffer[3] = 0x03; + + switch (*((uint32_t *)buffer)) + { + case 0x00010203: + return Endianness_Big; + + case 0x03020100: + return Endianness_Little; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } }
--- a/Core/Toolbox.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Toolbox.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,6 +32,8 @@ #pragma once +#include "Enumerations.h" + #include <stdint.h> #include <vector> #include <string> @@ -55,6 +57,9 @@ void ReadFile(std::string& content, const std::string& path); + void WriteFile(const std::string& content, + const std::string& path); + void Sleep(uint32_t seconds); void USleep(uint64_t microSeconds); @@ -80,6 +85,8 @@ void ComputeSHA1(std::string& result, const std::string& data); + bool IsSHA1(const std::string& str); + std::string DecodeBase64(const std::string& data); std::string EncodeBase64(const std::string& data); @@ -99,5 +106,7 @@ // In-place percent-decoding for URL void UrlDecode(std::string& s); + + Endianness DetectEndianness(); } }
--- a/Core/Uuid.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Uuid.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -96,6 +96,28 @@ } + bool StartsWithUuid(const std::string& str) + { + if (str.size() < 36) + { + return false; + } + + if (str.size() == 36) + { + return IsUuid(str); + } + + assert(str.size() > 36); + if (!isspace(str[36])) + { + return false; + } + + return IsUuid(str.substr(0, 36)); + } + + static std::string CreateTemporaryPath(const char* extension) { #if BOOST_HAS_FILESYSTEM_V3 == 1
--- a/Core/Uuid.h Mon Apr 29 12:48:10 2013 +0200 +++ b/Core/Uuid.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -51,6 +51,8 @@ bool IsUuid(const std::string& str); + bool StartsWithUuid(const std::string& str); + class TemporaryFile { private:
--- a/NEWS Mon Apr 29 12:48:10 2013 +0200 +++ b/NEWS Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,62 @@ Pending changes in the mainline =============================== +* Switch to Boost 1.54.0 (cf. issue #9) + + +Version 0.6.1 (2013/09/16) +========================== + +* Detection of stable patients/studies/series +* C-Find SCU at the instance level +* Link from modified to original resource in Orthanc Explorer +* Fix of issue #8 +* Anonymization of the medical alerts tag (0010,2000) + + +Version 0.6.0 (2013/07/16) +========================== + +Major changes +------------- + +* Introduction of the C++ client +* Send DICOM resources to other Orthanc instances through HTTP +* Access to signed images (instances/.../image-int16) + (Closes: Debian #716958) + +Minor changes +------------- + +* Export of DICOM files to the host filesystem (instances/.../export) +* Statistics about patients, studies, series and instances +* Link from anonymized to original resource in Orthanc Explorer +* Fixes for Red Hat and Debian packaging +* Fixes for history in Orthanc Explorer +* Fixes for boost::thread, as reported by Cyril Paulus +* Fix licensing (Closes: Debian #712038) + +Metadata +-------- + +* Access to the metadata through the REST API (.../metadata) +* Support of user-defined metadata +* "LastUpdate" metadata for patients, studies and series +* "/tools/now" to be used in combination with "LastUpdate" +* Improved support of series with temporal positions + + +Version 0.5.2 (2013/05/07) +========================== + +* "Bulk" Store-SCU (send several DICOM instances with the same + DICOM connexion) +* Store-SCU for patients and studies in Orthanc Explorer +* Filtering of incoming DICOM instances (through Lua scripting) +* Filtering of incoming HTTP requests (through Lua scripting) +* Clearing of "/exports" and "/changes" +* Check MD5 of third party downloads +* Faking of the HTTP methods PUT and DELETE Version 0.5.1 (2013/04/17) @@ -9,7 +65,7 @@ * Support of RGB images * Fix of store SCU in release builds * Possibility to store the SQLite index at another place than the - DICOM instances + DICOM instances (for performance) Version 0.5.0 (2013/01/31)
--- a/OrthancCppClient/CMakeLists.txt Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Mini-project to check whether "OrthancCppClient" can compile in a -# standalone fashion - -cmake_minimum_required(VERSION 2.8) - -project(OrthancCppClientTest) - -SET(STATIC_BUILD OFF) - -include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DownloadPackage.cmake) -include(${CMAKE_SOURCE_DIR}/../Resources/CMake/JsonCppConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/../Resources/CMake/LibCurlConfiguration.cmake) - -if (${CMAKE_COMPILER_IS_GNUCXX}) - set(CMAKE_C_FLAGS "-Wall -pedantic -Wno-implicit-function-declaration") # --std=c99 makes libcurl not to compile - set(CMAKE_CXX_FLAGS "-Wall -pedantic -Wno-long-long -Wno-variadic-macros") - set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed") - set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") -elseif (${MSVC}) - add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) -endif() - -add_library(OrthancCppClient - SHARED - - ${THIRD_PARTY_SOURCES} - HttpException.cpp - HttpClient.cpp - ) - -add_executable(Test - main.cpp - ) - -target_link_libraries(Test OrthancCppClient)
--- a/OrthancCppClient/HttpClient.cpp Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include "HttpClient.h" - -#include <string.h> -#include <curl/curl.h> - - -namespace Orthanc -{ - struct HttpClient::PImpl - { - CURL* curl_; - struct curl_slist *postHeaders_; - }; - - - static CURLcode CheckCode(CURLcode code) - { - if (code != CURLE_OK) - { - printf("ICI: %s\n", curl_easy_strerror(code)); - throw HttpException("CURL: " + std::string(curl_easy_strerror(code))); - } - - return code; - } - - - static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *payload) - { - std::string& target = *(static_cast<std::string*>(payload)); - - size_t length = size * nmemb; - if (length == 0) - return 0; - - size_t pos = target.size(); - - target.resize(pos + length); - memcpy(&target.at(pos), buffer, length); - - return length; - } - - - HttpClient::HttpClient() : pimpl_(new PImpl) - { - pimpl_->postHeaders_ = NULL; - if ((pimpl_->postHeaders_ = curl_slist_append(pimpl_->postHeaders_, "Expect:")) == NULL) - { - throw HttpException("HttpClient: Not enough memory"); - } - - pimpl_->curl_ = curl_easy_init(); - if (!pimpl_->curl_) - { - curl_slist_free_all(pimpl_->postHeaders_); - throw HttpException("HttpClient: Not enough memory"); - } - - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlCallback)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); - -#if ORTHANC_SSL_ENABLED == 1 - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); -#endif - - url_ = ""; - method_ = Orthanc_HttpMethod_Get; - lastStatus_ = Orthanc_HttpStatus_200_Ok; - isVerbose_ = false; - } - - - HttpClient::~HttpClient() - { - curl_easy_cleanup(pimpl_->curl_); - curl_slist_free_all(pimpl_->postHeaders_); - } - - - void HttpClient::SetVerbose(bool isVerbose) - { - isVerbose_ = isVerbose; - - if (isVerbose_) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1)); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0)); - } - } - - - bool HttpClient::Apply(std::string& answer) - { - answer.clear(); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL)); - - switch (method_) - { - case Orthanc_HttpMethod_Get: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L)); - break; - - case Orthanc_HttpMethod_Post: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); - - if (postData_.size() > 0) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, postData_.size())); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); - } - - break; - - case Orthanc_HttpMethod_Delete: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); - break; - - case Orthanc_HttpMethod_Put: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); - break; - - default: - throw HttpException("HttpClient: Internal error"); - } - - // Do the actual request - CheckCode(curl_easy_perform(pimpl_->curl_)); - - long status; - CheckCode(curl_easy_getinfo(pimpl_->curl_, CURLINFO_RESPONSE_CODE, &status)); - - if (status == 0) - { - // This corresponds to a call to an inexistent host - lastStatus_ = Orthanc_HttpStatus_500_InternalServerError; - } - else - { - lastStatus_ = static_cast<Orthanc_HttpStatus>(status); - } - - return (status >= 200 && status < 300); - } - - - bool HttpClient::Apply(Json::Value& answer) - { - std::string s; - if (Apply(s)) - { - Json::Reader reader; - return reader.parse(s, answer); - } - else - { - return false; - } - } - - - void HttpClient::SetPassword(const char* username, - const char* password) - { - std::string s = std::string(username) + ":" + std::string(password); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, s.c_str())); - } -}
--- a/OrthancCppClient/HttpClient.h Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#pragma once - -#include "HttpEnumerations.h" -#include "HttpException.h" - -#include <string> -#include <boost/shared_ptr.hpp> -#include <json/json.h> - -namespace Orthanc -{ - class HttpClient - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - std::string url_; - Orthanc_HttpMethod method_; - Orthanc_HttpStatus lastStatus_; - std::string postData_; - bool isVerbose_; - - public: - HttpClient(); - - ~HttpClient(); - - void SetUrl(const char* url) - { - url_ = std::string(url); - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - const std::string& GetUrl() const - { - return url_; - } - - void SetMethod(Orthanc_HttpMethod method) - { - method_ = method; - } - - Orthanc_HttpMethod GetMethod() const - { - return method_; - } - - std::string& AccessPostData() - { - return postData_; - } - - const std::string& AccessPostData() const - { - return postData_; - } - - void SetVerbose(bool isVerbose); - - bool IsVerbose() const - { - return isVerbose_; - } - - bool Apply(std::string& answer); - - bool Apply(Json::Value& answer); - - Orthanc_HttpStatus GetLastStatus() const - { - return lastStatus_; - } - - const char* GetLastStatusText() const - { - return HttpException::GetDescription(lastStatus_); - } - - void SetPassword(const char* username, - const char* password); - }; -}
--- a/OrthancCppClient/HttpEnumerations.h Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#pragma once - - -/** - * This file contains the enumerations for the access to the Orthanc - * REST API in C and C++. Namespaces are not used, in order to enable - * the access in C. - **/ - -// Most common, non-joke and non-experimental HTTP status codes -// http://en.wikipedia.org/wiki/List_of_HTTP_status_codes -enum Orthanc_HttpStatus -{ - Orthanc_HttpStatus_None = -1, - - // 1xx Informational - Orthanc_HttpStatus_100_Continue = 100, - Orthanc_HttpStatus_101_SwitchingProtocols = 101, - Orthanc_HttpStatus_102_Processing = 102, - - // 2xx Success - Orthanc_HttpStatus_200_Ok = 200, - Orthanc_HttpStatus_201_Created = 201, - Orthanc_HttpStatus_202_Accepted = 202, - Orthanc_HttpStatus_203_NonAuthoritativeInformation = 203, - Orthanc_HttpStatus_204_NoContent = 204, - Orthanc_HttpStatus_205_ResetContent = 205, - Orthanc_HttpStatus_206_PartialContent = 206, - Orthanc_HttpStatus_207_MultiStatus = 207, - Orthanc_HttpStatus_208_AlreadyReported = 208, - Orthanc_HttpStatus_226_IMUsed = 226, - - // 3xx Redirection - Orthanc_HttpStatus_300_MultipleChoices = 300, - Orthanc_HttpStatus_301_MovedPermanently = 301, - Orthanc_HttpStatus_302_Found = 302, - Orthanc_HttpStatus_303_SeeOther = 303, - Orthanc_HttpStatus_304_NotModified = 304, - Orthanc_HttpStatus_305_UseProxy = 305, - Orthanc_HttpStatus_307_TemporaryRedirect = 307, - - // 4xx Client Error - Orthanc_HttpStatus_400_BadRequest = 400, - Orthanc_HttpStatus_401_Unauthorized = 401, - Orthanc_HttpStatus_402_PaymentRequired = 402, - Orthanc_HttpStatus_403_Forbidden = 403, - Orthanc_HttpStatus_404_NotFound = 404, - Orthanc_HttpStatus_405_MethodNotAllowed = 405, - Orthanc_HttpStatus_406_NotAcceptable = 406, - Orthanc_HttpStatus_407_ProxyAuthenticationRequired = 407, - Orthanc_HttpStatus_408_RequestTimeout = 408, - Orthanc_HttpStatus_409_Conflict = 409, - Orthanc_HttpStatus_410_Gone = 410, - Orthanc_HttpStatus_411_LengthRequired = 411, - Orthanc_HttpStatus_412_PreconditionFailed = 412, - Orthanc_HttpStatus_413_RequestEntityTooLarge = 413, - Orthanc_HttpStatus_414_RequestUriTooLong = 414, - Orthanc_HttpStatus_415_UnsupportedMediaType = 415, - Orthanc_HttpStatus_416_RequestedRangeNotSatisfiable = 416, - Orthanc_HttpStatus_417_ExpectationFailed = 417, - Orthanc_HttpStatus_422_UnprocessableEntity = 422, - Orthanc_HttpStatus_423_Locked = 423, - Orthanc_HttpStatus_424_FailedDependency = 424, - Orthanc_HttpStatus_426_UpgradeRequired = 426, - - // 5xx Server Error - Orthanc_HttpStatus_500_InternalServerError = 500, - Orthanc_HttpStatus_501_NotImplemented = 501, - Orthanc_HttpStatus_502_BadGateway = 502, - Orthanc_HttpStatus_503_ServiceUnavailable = 503, - Orthanc_HttpStatus_504_GatewayTimeout = 504, - Orthanc_HttpStatus_505_HttpVersionNotSupported = 505, - Orthanc_HttpStatus_506_VariantAlsoNegotiates = 506, - Orthanc_HttpStatus_507_InsufficientStorage = 507, - Orthanc_HttpStatus_509_BandwidthLimitExceeded = 509, - Orthanc_HttpStatus_510_NotExtended = 510 -}; - - -enum Orthanc_HttpMethod -{ - Orthanc_HttpMethod_Get = 0, - Orthanc_HttpMethod_Post = 1, - Orthanc_HttpMethod_Delete = 2, - Orthanc_HttpMethod_Put = 3 -};
--- a/OrthancCppClient/HttpException.cpp Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include "HttpException.h" - -namespace Orthanc -{ - const char* HttpException::What() const - { - if (status_ == Orthanc_HttpStatus_None) - { - return custom_.c_str(); - } - else - { - return GetDescription(status_); - } - } - - const char* HttpException::GetDescription(Orthanc_HttpStatus status) - { - switch (status) - { - case Orthanc_HttpStatus_100_Continue: - return "Continue"; - - case Orthanc_HttpStatus_101_SwitchingProtocols: - return "Switching Protocols"; - - case Orthanc_HttpStatus_102_Processing: - return "Processing"; - - case Orthanc_HttpStatus_200_Ok: - return "OK"; - - case Orthanc_HttpStatus_201_Created: - return "Created"; - - case Orthanc_HttpStatus_202_Accepted: - return "Accepted"; - - case Orthanc_HttpStatus_203_NonAuthoritativeInformation: - return "Non-Authoritative Information"; - - case Orthanc_HttpStatus_204_NoContent: - return "No Content"; - - case Orthanc_HttpStatus_205_ResetContent: - return "Reset Content"; - - case Orthanc_HttpStatus_206_PartialContent: - return "Partial Content"; - - case Orthanc_HttpStatus_207_MultiStatus: - return "Multi-Status"; - - case Orthanc_HttpStatus_208_AlreadyReported: - return "Already Reported"; - - case Orthanc_HttpStatus_226_IMUsed: - return "IM Used"; - - case Orthanc_HttpStatus_300_MultipleChoices: - return "Multiple Choices"; - - case Orthanc_HttpStatus_301_MovedPermanently: - return "Moved Permanently"; - - case Orthanc_HttpStatus_302_Found: - return "Found"; - - case Orthanc_HttpStatus_303_SeeOther: - return "See Other"; - - case Orthanc_HttpStatus_304_NotModified: - return "Not Modified"; - - case Orthanc_HttpStatus_305_UseProxy: - return "Use Proxy"; - - case Orthanc_HttpStatus_307_TemporaryRedirect: - return "Temporary Redirect"; - - case Orthanc_HttpStatus_400_BadRequest: - return "Bad Request"; - - case Orthanc_HttpStatus_401_Unauthorized: - return "Unauthorized"; - - case Orthanc_HttpStatus_402_PaymentRequired: - return "Payment Required"; - - case Orthanc_HttpStatus_403_Forbidden: - return "Forbidden"; - - case Orthanc_HttpStatus_404_NotFound: - return "Not Found"; - - case Orthanc_HttpStatus_405_MethodNotAllowed: - return "Method Not Allowed"; - - case Orthanc_HttpStatus_406_NotAcceptable: - return "Not Acceptable"; - - case Orthanc_HttpStatus_407_ProxyAuthenticationRequired: - return "Proxy Authentication Required"; - - case Orthanc_HttpStatus_408_RequestTimeout: - return "Request Timeout"; - - case Orthanc_HttpStatus_409_Conflict: - return "Conflict"; - - case Orthanc_HttpStatus_410_Gone: - return "Gone"; - - case Orthanc_HttpStatus_411_LengthRequired: - return "Length Required"; - - case Orthanc_HttpStatus_412_PreconditionFailed: - return "Precondition Failed"; - - case Orthanc_HttpStatus_413_RequestEntityTooLarge: - return "Request Entity Too Large"; - - case Orthanc_HttpStatus_414_RequestUriTooLong: - return "Request-URI Too Long"; - - case Orthanc_HttpStatus_415_UnsupportedMediaType: - return "Unsupported Media Type"; - - case Orthanc_HttpStatus_416_RequestedRangeNotSatisfiable: - return "Requested Range Not Satisfiable"; - - case Orthanc_HttpStatus_417_ExpectationFailed: - return "Expectation Failed"; - - case Orthanc_HttpStatus_422_UnprocessableEntity: - return "Unprocessable Entity"; - - case Orthanc_HttpStatus_423_Locked: - return "Locked"; - - case Orthanc_HttpStatus_424_FailedDependency: - return "Failed Dependency"; - - case Orthanc_HttpStatus_426_UpgradeRequired: - return "Upgrade Required"; - - case Orthanc_HttpStatus_500_InternalServerError: - return "Internal Server Error"; - - case Orthanc_HttpStatus_501_NotImplemented: - return "Not Implemented"; - - case Orthanc_HttpStatus_502_BadGateway: - return "Bad Gateway"; - - case Orthanc_HttpStatus_503_ServiceUnavailable: - return "Service Unavailable"; - - case Orthanc_HttpStatus_504_GatewayTimeout: - return "Gateway Timeout"; - - case Orthanc_HttpStatus_505_HttpVersionNotSupported: - return "HTTP Version Not Supported"; - - case Orthanc_HttpStatus_506_VariantAlsoNegotiates: - return "Variant Also Negotiates"; - - case Orthanc_HttpStatus_507_InsufficientStorage: - return "Insufficient Storage"; - - case Orthanc_HttpStatus_509_BandwidthLimitExceeded: - return "Bandwidth Limit Exceeded"; - - case Orthanc_HttpStatus_510_NotExtended: - return "Not Extended"; - - default: - throw HttpException("Unknown HTTP status"); - } - } -}
--- a/OrthancCppClient/HttpException.h Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#pragma once - -#include "HttpEnumerations.h" - -#include <string> - -namespace Orthanc -{ - class HttpException - { - private: - Orthanc_HttpStatus status_; - std::string custom_; - - public: - static const char* GetDescription(Orthanc_HttpStatus status); - - HttpException(const std::string& custom) - { - status_ = Orthanc_HttpStatus_None; - custom_ = custom; - } - - HttpException(Orthanc_HttpStatus status) - { - status_ = status; - } - - Orthanc_HttpStatus GetHttpStatus() const - { - return status_; - } - - const char* What() const; - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Instance.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,224 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "Instance.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +#include <boost/lexical_cast.hpp> + +namespace OrthancClient +{ + void Instance::DownloadImage() + { + if (reader_.get() == NULL) + { + const char* suffix; + switch (mode_) + { + case Orthanc::ImageExtractionMode_Preview: + suffix = "preview"; + break; + + case Orthanc::ImageExtractionMode_UInt8: + suffix = "image-uint8"; + break; + + case Orthanc::ImageExtractionMode_UInt16: + suffix = "image-uint16"; + break; + + case Orthanc::ImageExtractionMode_Int16: + suffix = "image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(connection_.GetOrthancUrl() + "/instances/" + id_ + "/" + suffix); + std::string png; + + if (!client.Apply(png)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + reader_.reset(new Orthanc::PngReader); + reader_->ReadFromMemory(png); + } + } + + Instance::Instance(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + mode_(Orthanc::ImageExtractionMode_Int16) + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + + client.SetUrl(connection_.GetOrthancUrl() + "/instances/" + id_ + "/simplified-tags"); + Json::Value v; + if (!client.Apply(tags_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + std::string Instance::GetTagAsString(const char* tag) + { + if (tags_.isMember(tag)) + { + return tags_[tag].asString(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); + } + } + + float Instance::GetTagAsFloat(const char* tag) + { + std::string value = GetTagAsString(tag); + + try + { + return boost::lexical_cast<float>(value); + } + catch (boost::bad_lexical_cast) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + int Instance::GetTagAsInt(const char* tag) + { + std::string value = GetTagAsString(tag); + + try + { + return boost::lexical_cast<int>(value); + } + catch (boost::bad_lexical_cast) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + unsigned int Instance::GetWidth() + { + DownloadImage(); + return reader_->GetWidth(); + } + + unsigned int Instance::GetHeight() + { + DownloadImage(); + return reader_->GetHeight(); + } + + unsigned int Instance::GetPitch() + { + DownloadImage(); + return reader_->GetPitch(); + } + + Orthanc::PixelFormat Instance::GetPixelFormat() + { + DownloadImage(); + return reader_->GetFormat(); + } + + const void* Instance::GetBuffer() + { + DownloadImage(); + return reader_->GetBuffer(); + } + + const void* Instance::GetBuffer(unsigned int y) + { + DownloadImage(); + return reader_->GetBuffer(y); + } + + void Instance::DiscardImage() + { + reader_.reset(); + } + + + void Instance::SetImageExtractionMode(Orthanc::ImageExtractionMode mode) + { + if (mode_ == mode) + { + return; + } + + DiscardImage(); + mode_ = mode; + } + + + void Instance::SplitVectorOfFloats(std::vector<float>& target, + const char* tag) + { + const std::string value = GetTagAsString(tag); + + target.clear(); + + try + { + std::string tmp; + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == '\\') + { + target.push_back(boost::lexical_cast<float>(tmp)); + tmp.clear(); + } + else + { + tmp.push_back(value[i]); + } + } + + target.push_back(boost::lexical_cast<float>(tmp)); + } + catch (boost::bad_lexical_cast) + { + // Unable to parse the Image Orientation Patient. + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Instance.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,95 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include <json/value.h> + +#include "../Core/IDynamicObject.h" +#include "../Core/FileFormats/PngReader.h" + +namespace OrthancClient +{ + class OrthancConnection; + + class Instance : public Orthanc::IDynamicObject + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value tags_; + std::auto_ptr<Orthanc::PngReader> reader_; + Orthanc::ImageExtractionMode mode_; + + void DownloadImage(); + + public: + Instance(const OrthancConnection& connection, + const std::string& id); + + const std::string& GetId() const + { + return id_; + } + + void SetImageExtractionMode(Orthanc::ImageExtractionMode mode); + + Orthanc::ImageExtractionMode GetImageExtractionMode() const + { + return mode_; + } + + std::string GetTagAsString(const char* tag); + + float GetTagAsFloat(const char* tag); + + int GetTagAsInt(const char* tag); + + unsigned int GetWidth(); + + unsigned int GetHeight(); + + unsigned int GetPitch(); + + Orthanc::PixelFormat GetPixelFormat(); + + const void* GetBuffer(); + + const void* GetBuffer(unsigned int y); + + void DiscardImage(); + + void SplitVectorOfFloats(std::vector<float>& target, + const char* tag); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancConnection.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,76 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancConnection.h" + +#include "../Core/OrthancException.h" + +namespace OrthancClient +{ + void OrthancConnection::ReadPatients() + { + client_.SetUrl(orthancUrl_ + "/patients"); + Json::Value v; + if (!client_.Apply(content_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + return new Patient(*this, content_[tmp].asString()); + } + + Patient& OrthancConnection::GetPatient(unsigned int index) + { + return dynamic_cast<Patient&>(patients_.GetItem(index)); + } + + OrthancConnection::OrthancConnection(const char* orthancUrl) : + orthancUrl_(orthancUrl), patients_(*this) + { + ReadPatients(); + } + + OrthancConnection::OrthancConnection(const char* orthancUrl, + const char* username, + const char* password) : + orthancUrl_(orthancUrl), patients_(*this) + { + client_.SetCredentials(username, password); + ReadPatients(); + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancConnection.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/HttpClient.h" + +#include "Patient.h" + +namespace OrthancClient +{ + class OrthancConnection : + public boost::noncopyable, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + Orthanc::HttpClient client_; + std::string orthancUrl_; + Orthanc::ArrayFilledByThreads patients_; + Json::Value content_; + + void ReadPatients(); + + virtual size_t GetFillerSize() + { + return content_.size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + OrthancConnection(const char* orthancUrl); + + OrthancConnection(const char* orthancUrl, + const char* username, + const char* password); + + unsigned int GetThreadCount() const + { + return patients_.GetThreadCount(); + } + + void SetThreadCount(unsigned int threadCount) + { + patients_.SetThreadCount(threadCount); + } + + void Reload() + { + patients_.Reload(); + } + + const Orthanc::HttpClient& GetHttpClient() const + { + return client_; + } + + const std::string& GetOrthancUrl() const + { + return orthancUrl_; + } + + unsigned int GetPatientCount() + { + return patients_.GetSize(); + } + + Patient& GetPatient(unsigned int index); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Patient.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "Patient.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +namespace OrthancClient +{ + void Patient::ReadPatient() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(connection_.GetOrthancUrl() + "/patients/" + id_); + Json::Value v; + if (!client.Apply(patient_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + return new Study(connection_, patient_["Studies"][tmp].asString()); + } + + Patient::Patient(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + studies_(*this) + { + studies_.SetThreadCount(connection.GetThreadCount()); + ReadPatient(); + } + + std::string Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (patient_["MainDicomTags"].isMember(tag)) + { + return patient_["MainDicomTags"][tag].asString(); + } + else + { + return defaultValue; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Patient.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Study.h" + +namespace OrthancClient +{ + class Patient : + public Orthanc::IDynamicObject, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value patient_; + Orthanc::ArrayFilledByThreads studies_; + + void ReadPatient(); + + virtual size_t GetFillerSize() + { + return patient_["Studies"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + Patient(const OrthancConnection& connection, + const std::string& id); + + void Reload() + { + studies_.Reload(); + } + + unsigned int GetStudyCount() + { + return studies_.GetSize(); + } + + Study& GetStudy(unsigned int index) + { + return dynamic_cast<Study&>(studies_.GetItem(index)); + } + + const std::string& GetId() const + { + return id_; + } + + std::string GetMainDicomTag(const char* tag, + const char* defaultValue) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Series.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,432 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "Series.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +#include <set> +#include <boost/lexical_cast.hpp> + +namespace OrthancClient +{ + namespace + { + class SliceLocator + { + private: + float normal_[3]; + + public: + SliceLocator(Instance& someSlice) + { + /** + * Compute the slice normal from Image Orientation Patient. + * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice + * http://www.itk.org/pipermail/insight-users/2003-September/004762.html + **/ + + std::vector<float> cosines; + someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); + + if (cosines.size() != 6) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; + normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; + normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; + } + + + /** + * Compute the distance of some slice along the slice normal. + **/ + float ComputeSliceLocation(Instance& instance) const + { + std::vector<float> ipp; + instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); + if (ipp.size() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + float dist = 0; + + for (int i = 0; i < 3; i++) + { + dist += normal_[i] * ipp[i]; + } + + return dist; + } + }; + + class ImageDownloadCommand : public Orthanc::ICommand + { + private: + Orthanc::PixelFormat format_; + Orthanc::ImageExtractionMode mode_; + Instance& instance_; + void* target_; + size_t lineStride_; + + public: + ImageDownloadCommand(Instance& instance, + Orthanc::PixelFormat format, + Orthanc::ImageExtractionMode mode, + void* target, + size_t lineStride) : + format_(format), + mode_(mode), + instance_(instance), + target_(target), + lineStride_(lineStride) + { + instance_.SetImageExtractionMode(mode); + } + + virtual bool Execute() + { + using namespace Orthanc; + + unsigned int width = instance_.GetHeight(); + + for (unsigned int y = 0; y < instance_.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_; + + if (instance_.GetPixelFormat() == format_) + { + memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth()); + } + else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 && + format_ == PixelFormat_RGB24) + { + const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y)); + for (unsigned int x = 0; x < width; x++, s++, p += 3) + { + p[0] = *s; + p[1] = *s; + p[2] = *s; + } + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + + // Do not keep the image in memory, as we are loading 3D images + instance_.DiscardImage(); + + return true; + } + }; + } + + + void Series::Check3DImage() + { + if (!Is3DImage()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + bool Series::Is3DImageInternal() + { + try + { + if (GetInstanceCount() == 0) + { + return true; + } + + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + if (GetInstance(0).GetTagAsString("Columns") != GetInstance(i).GetTagAsString("Columns") || + GetInstance(0).GetTagAsString("Rows") != GetInstance(i).GetTagAsString("Rows") || + GetInstance(0).GetTagAsString("ImageOrientationPatient") != GetInstance(i).GetTagAsString("ImageOrientationPatient") || + GetInstance(0).GetTagAsString("SliceThickness") != GetInstance(i).GetTagAsString("SliceThickness") || + GetInstance(0).GetTagAsString("PixelSpacing") != GetInstance(i).GetTagAsString("PixelSpacing")) + { + return false; + } + } + + SliceLocator locator(GetInstance(0)); + std::set<float> l; + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + l.insert(locator.ComputeSliceLocation(GetInstance(i))); + } + + return l.size() == GetInstanceCount(); + } + catch (Orthanc::OrthancException) + { + return false; + } + } + + void Series::ReadSeries() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + + client.SetUrl(connection_.GetOrthancUrl() + "/series/" + id_); + Json::Value v; + if (!client.Apply(series_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Series::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + return new Instance(connection_, series_["Instances"][tmp].asString()); + } + + Series::Series(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + instances_(*this) + { + ReadSeries(); + status_ = Status3DImage_NotTested; + + instances_.SetThreadCount(connection.GetThreadCount()); + } + + + bool Series::Is3DImage() + { + if (status_ == Status3DImage_NotTested) + { + status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False; + } + + return status_ == Status3DImage_True; + } + + unsigned int Series::GetInstanceCount() + { + return instances_.GetSize(); + } + + Instance& Series::GetInstance(unsigned int index) + { + return dynamic_cast<Instance&>(instances_.GetItem(index)); + } + + std::string Series::GetUrl() const + { + return connection_.GetOrthancUrl() + "/series/" + id_; + } + + unsigned int Series::GetWidth() + { + Check3DImage(); + + if (GetInstanceCount() == 0) + return 0; + else + return GetInstance(0).GetTagAsInt("Columns"); + } + + unsigned int Series::GetHeight() + { + Check3DImage(); + + if (GetInstanceCount() == 0) + return 0; + else + return GetInstance(0).GetTagAsInt("Rows"); + } + + void Series::GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ) + { + Check3DImage(); + + if (GetInstanceCount() == 0) + { + sizeX = 0; + sizeY = 0; + sizeZ = 0; + } + else + { + try + { + std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); + size_t pos = s.find('\\'); + assert(pos != std::string::npos); + std::string sy = s.substr(0, pos); + std::string sx = s.substr(pos + 1); + + sizeX = boost::lexical_cast<float>(sx); + sizeY = boost::lexical_cast<float>(sy); + sizeZ = GetInstance(0).GetTagAsFloat("SliceThickness"); + } + catch (boost::bad_lexical_cast) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + + std::string Series::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (series_["MainDicomTags"].isMember(tag)) + { + return series_["MainDicomTags"][tag].asString(); + } + else + { + return defaultValue; + } + } + + + + void Series::Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener* listener) + { + using namespace Orthanc; + + // Choose the extraction mode, depending on the format of the + // target image. + + uint8_t bytesPerPixel; + ImageExtractionMode mode; + + switch (format) + { + case PixelFormat_RGB24: + bytesPerPixel = 3; + mode = ImageExtractionMode_Preview; + break; + + case PixelFormat_Grayscale8: + bytesPerPixel = 1; + mode = ImageExtractionMode_UInt8; // Preview ??? + break; + + case PixelFormat_Grayscale16: + bytesPerPixel = 2; + mode = ImageExtractionMode_UInt16; + break; + + case PixelFormat_SignedGrayscale16: + bytesPerPixel = 2; + mode = ImageExtractionMode_UInt16; + format = PixelFormat_Grayscale16; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + + // Check that the target image is properly sized + unsigned int sx = GetWidth(); + unsigned int sy = GetHeight(); + + if (lineStride < sx * bytesPerPixel || + stackStride < sx * sy * bytesPerPixel) + { + throw OrthancException(ErrorCode_BadRequest); + } + + if (sx == 0 || sy == 0 || GetInstanceCount() == 0) + { + // Empty image, nothing to do + if (listener) + listener->SignalSuccess(0); + return; + } + + + /** + * Order the stacks according to their distance along the slice + * normal (using the "Image Position Patient" tag). This works + * even if the "SliceLocation" tag is absent. + **/ + SliceLocator locator(GetInstance(0)); + + typedef std::map<float, Instance*> Instances; + Instances instances; + for (unsigned int i = 0; i < GetInstanceCount(); i++) + { + float dist = locator.ComputeSliceLocation(GetInstance(i)); + instances[dist] = &GetInstance(i); + } + + if (instances.size() != GetInstanceCount()) + { + // Several instances have the same Z coordinate + throw OrthancException(ErrorCode_NotImplemented); + } + + + // Submit the download of each stack as a set of commands + ThreadedCommandProcessor processor(connection_.GetThreadCount()); + + if (listener != NULL) + { + processor.SetListener(*listener); + } + + uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target); + for (Instances::iterator it = instances.begin(); it != instances.end(); it++) + { + processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride)); + stackTarget += stackStride; + } + + + // Wait for all the stacks to be downloaded + if (!processor.Join()) + { + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Series.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,127 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Instance.h" + +#include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/ThreadedCommandProcessor.h" + +namespace OrthancClient +{ + class Series : + public Orthanc::IDynamicObject, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + enum Status3DImage + { + Status3DImage_NotTested, + Status3DImage_True, + Status3DImage_False + }; + + const OrthancConnection& connection_; + std::string id_; + Json::Value series_; + Orthanc::ArrayFilledByThreads instances_; + Status3DImage status_; + + void Check3DImage(); + + bool Is3DImageInternal(); + + void ReadSeries(); + + virtual size_t GetFillerSize() + { + return series_["Instances"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + void Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener* listener); + + public: + Series(const OrthancConnection& connection, + const std::string& id); + + void Reload() + { + instances_.Reload(); + } + + bool Is3DImage(); + + unsigned int GetInstanceCount(); + + Instance& GetInstance(unsigned int index); + + const std::string& GetId() const + { + return id_; + } + + std::string GetUrl() const; + + unsigned int GetWidth(); + + unsigned int GetHeight(); + + void GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ); + + std::string GetMainDicomTag(const char* tag, + const char* defaultValue) const; + + void Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride, + Orthanc::ThreadedCommandProcessor::IListener& listener) + { + Load3DImage(target, format, lineStride, stackStride, &listener); + } + + void Load3DImage(void* target, + Orthanc::PixelFormat format, + size_t lineStride, + size_t stackStride) + { + Load3DImage(target, format, lineStride, stackStride, NULL); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Study.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "Study.h" + +#include "OrthancConnection.h" +#include "../Core/OrthancException.h" + +namespace OrthancClient +{ + void Study::ReadStudy() + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(connection_.GetOrthancUrl() + "/studies/" + id_); + Json::Value v; + if (!client.Apply(study_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + Orthanc::IDynamicObject* Study::GetFillerItem(size_t index) + { + Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); + return new Series(connection_, study_["Series"][tmp].asString()); + } + + Study::Study(const OrthancConnection& connection, + const std::string& id) : + connection_(connection), + id_(id), + series_(*this) + { + series_.SetThreadCount(connection.GetThreadCount()); + ReadStudy(); + } + + std::string Study::GetMainDicomTag(const char* tag, const char* defaultValue) const + { + if (study_["MainDicomTags"].isMember(tag)) + { + return study_["MainDicomTags"][tag].asString(); + } + else + { + return defaultValue; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/Study.h Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Series.h" + +namespace OrthancClient +{ + class Study : + public Orthanc::IDynamicObject, + private Orthanc::ArrayFilledByThreads::IFiller + { + private: + const OrthancConnection& connection_; + std::string id_; + Json::Value study_; + Orthanc::ArrayFilledByThreads series_; + + void ReadStudy(); + + virtual size_t GetFillerSize() + { + return study_["Series"].size(); + } + + virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); + + public: + Study(const OrthancConnection& connection, + const std::string& id); + + void Reload() + { + series_.Reload(); + } + + unsigned int GetSeriesCount() + { + return series_.GetSize(); + } + + Series& GetSeries(unsigned int index) + { + return dynamic_cast<Series&>(series_.GetItem(index)); + } + + const std::string& GetId() const + { + return id_; + } + + std::string GetMainDicomTag(const char* tag, + const char* defaultValue) const; + }; +}
--- a/OrthancCppClient/main.cpp Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, - * Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include "HttpClient.h" - -#include <iostream> - -int main() -{ - // Prepare a simple call to a Web service - Orthanc::HttpClient c; - c.SetUrl("http://nominatim.openstreetmap.org/search?format=json&q=chu+liege+belgium"); - - // Do the request and store the result in a JSON structure - Json::Value result; - c.Apply(result); - - // Display the JSON answer - std::cout << result << std::endl; - - return 0; -}
--- a/OrthancExplorer/explorer.html Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancExplorer/explorer.html Wed Sep 18 15:27:07 2013 +0200 @@ -88,11 +88,24 @@ <option value="on">Protected</option> </select> </div> - <!--a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a--> - <a href="#" data-role="button" data-icon="delete" id="patient-delete">Delete this patient</a> - <a href="#" data-role="button" data-icon="gear" id="patient-archive">Download ZIP</a> - <a href="#" data-role="button" data-icon="star" id="patient-anonymize">Anonymize</a> </p> + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="patient-delete">Delete this patient</a></li> + <li data-icon="forward"><a href="#" id="patient-store">Send to remote modality</a></li> + <li data-icon="star"><a href="#" id="patient-anonymize">Anonymize</a></li> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="patient-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="patient-modified-from">Before modification</a> + </li> + <li data-icon="gear"><a href="#" id="patient-archive">Download ZIP</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -121,11 +134,24 @@ <div style="padding-right:10px"> <ul data-role="listview" data-inset="true" data-theme="a" id="study-info"> </ul> - <p> - <a href="#" data-role="button" data-icon="delete" id="study-delete">Delete this study</a> - <a href="#" data-role="button" data-icon="gear" id="study-archive">Download ZIP</a> - <a href="#" data-role="button" data-icon="star" id="study-anonymize">Anonymize</a> - </p> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="study-delete">Delete this study</a></li> + <li data-icon="forward"><a href="#" id="study-store">Send to DICOM modality</a></li> + <li data-icon="star"><a href="#" id="study-anonymize">Anonymize</a></li> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="study-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="study-modified-from">Before modification</a> + </li> + <li data-icon="gear"><a href="#" id="study-archive">Download ZIP</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -154,15 +180,27 @@ <div class="ui-grid-a"> <div class="ui-block-a" style="width:30%"> <div style="padding-right:10px"> - <ul data-role="listview" data-inset="true" data-theme="a" id="series-info"> + <ul data-role="listview" data-inset="true" data-theme="a" id="series-info"> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="series-delete">Delete this series</a></li> + <li data-icon="forward"><a href="#" id="series-store">Send to DICOM modality</a></li> + <li data-icon="star"><a href="#" id="series-anonymize">Anonymize</a></li> </ul> - <p> - <a href="#" data-role="button" data-icon="delete" id="series-delete">Delete this series</a> - <a href="#" data-role="button" data-icon="search" id="series-preview">Preview this series</a> - <a href="#" data-role="button" data-icon="forward" id="series-store">Store in another DICOM modality</a> - <a href="#" data-role="button" data-icon="gear" id="series-archive">Download ZIP</a> - <a href="#" data-role="button" data-icon="star" id="series-anonymize">Anonymize</a> - </p> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="series-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="series-modified-from">Before modification</a> + </li> + <li data-icon="search"><a href="#" id="series-preview">Preview this series</a></li> + <li data-icon="gear"><a href="#" id="series-archive">Download ZIP</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -193,13 +231,25 @@ <div style="padding-right:10px"> <ul data-role="listview" data-inset="true" data-theme="a" id="instance-info"> </ul> - <p> - <a href="#" data-role="button" data-icon="delete" id="instance-delete">Delete this instance</a> - <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-dicom">Download the DICOM file</a> - <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-json">Download the JSON file</a> - <a href="#" data-role="button" data-icon="search" id="instance-preview">Preview the instance</a> - <a href="#" data-role="button" data-icon="forward" id="instance-store">Store in another DICOM modality</a> - </p> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Interact</li> + <li data-icon="delete"><a href="#" id="instance-delete">Delete this instance</a></li> + <li data-icon="forward"><a href="#" id="instance-store">Send to DICOM modality</a></li> + </ul> + + <ul data-role="listview" data-inset="true" data-theme="d" data-divider-theme="c"> + <li data-role="list-divider">Access</li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="instance-anonymized-from">Before anonymization</a> + </li> + <li data-icon="info" data-theme="e" style="display:none"> + <a href="#" id="instance-modified-from">Before modification</a> + </li> + <li data-icon="arrow-d"><a href="#" id="instance-download-dicom">Download the DICOM file</a></li> + <li data-icon="arrow-d"><a href="#" id="instance-download-json">Download the JSON file</a></li> + <li data-icon="search"><a href="#" id="instance-preview">Preview the instance</a></li> + </ul> </div> </div> <div class="ui-block-b" style="width:70%"> @@ -218,7 +268,12 @@ </div> </div> - <div id="loading" style="display:none;" class="ui-body-c"> + <div id="peer-store" style="display:none;" class="ui-body-c"> + <p align="center"><b>Sending to Orthanc peer...</b></p> + <p><img src="libs/images/ajax-loader2.gif" alt="" /></p> + </div> + + <div id="dicom-store" style="display:none;" class="ui-body-c"> <p align="center"><b>Sending to DICOM modality...</b></p> <p><img src="libs/images/ajax-loader2.gif" alt="" /></p> </div>
--- a/OrthancExplorer/explorer.js Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancExplorer/explorer.js Wed Sep 18 15:27:07 2013 +0200 @@ -12,6 +12,11 @@ //$.mobile.page.prototype.options.addBackBtn = true; //$.mobile.defaultPageTransition = 'slide'; + +var currentPage = ''; +var currentUuid = ''; + + // http://stackoverflow.com/a/4673436 String.prototype.format = function() { var args = arguments; @@ -356,7 +361,25 @@ -$('#patient').live('pagebeforeshow', function() { +function SetupAnonymizedOrModifiedFrom(buttonSelector, resource, resourceType, field) +{ + if (field in resource) + { + $(buttonSelector).closest('li').show(); + $(buttonSelector).click(function(e) { + window.location.assign('explorer.html#' + resourceType + '?uuid=' + resource[field]); + }); + } + else + { + $(buttonSelector).closest('li').hide(); + } +} + + + +function RefreshPatient() +{ if ($.mobile.pageData) { GetSingleResource('patients', $.mobile.pageData.uuid, function(patient) { GetMultipleResources('studies', patient.Studies, function(studies) { @@ -381,6 +404,9 @@ target.append(FormatStudy(studies[i], '#study?uuid=' + studies[i].ID)); } + SetupAnonymizedOrModifiedFrom('#patient-anonymized-from', patient, 'patient', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#patient-modified-from', patient, 'patient', 'ModifiedFrom'); + target.listview('refresh'); // Check whether this patient is protected @@ -395,13 +421,17 @@ $('#protection').val(v).slider('refresh'); } }); + + currentPage = 'patient'; + currentUuid = $.mobile.pageData.uuid; }); }); } -}); +} -$('#study').live('pagebeforeshow', function() { +function RefreshStudy() +{ if ($.mobile.pageData) { GetSingleResource('studies', $.mobile.pageData.uuid, function(study) { GetSingleResource('patients', study.ParentPatient, function(patient) { @@ -417,6 +447,9 @@ .append(FormatStudy(study)) .listview('refresh'); + SetupAnonymizedOrModifiedFrom('#study-anonymized-from', study, 'study', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#study-modified-from', study, 'study', 'ModifiedFrom'); + var target = $('#list-series'); $('li', target).remove(); for (var i = 0; i < series.length; i++) { @@ -428,14 +461,18 @@ target.append(FormatSeries(series[i], '#series?uuid=' + series[i].ID)); } target.listview('refresh'); + + currentPage = 'study'; + currentUuid = $.mobile.pageData.uuid; }); }); }); } -}); +} -$('#series').live('pagebeforeshow', function() { +function RefreshSeries() +{ if ($.mobile.pageData) { GetSingleResource('series', $.mobile.pageData.uuid, function(series) { GetSingleResource('studies', series.ParentStudy, function(study) { @@ -456,18 +493,24 @@ .append(FormatSeries(series)) .listview('refresh'); + SetupAnonymizedOrModifiedFrom('#series-anonymized-from', series, 'series', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#series-modified-from', series, 'series', 'ModifiedFrom'); + var target = $('#list-instances'); $('li', target).remove(); for (var i = 0; i < instances.length; i++) { target.append(FormatInstance(instances[i], '#instance?uuid=' + instances[i].ID)); } target.listview('refresh'); + + currentPage = 'series'; + currentUuid = $.mobile.pageData.uuid; }); }); }); }); } -}); +} @@ -522,7 +565,8 @@ } -$('#instance').live('pagebeforeshow', function() { +function RefreshInstance() +{ if ($.mobile.pageData) { GetSingleResource('instances', $.mobile.pageData.uuid, function(instance) { GetSingleResource('series', instance.ParentSeries, function(series) { @@ -554,15 +598,52 @@ } }); + SetupAnonymizedOrModifiedFrom('#instance-anonymized-from', instance, 'instance', 'AnonymizedFrom'); + SetupAnonymizedOrModifiedFrom('#instance-modified-from', instance, 'instance', 'ModifiedFrom'); + + currentPage = 'instance'; + currentUuid = $.mobile.pageData.uuid; }); }); }); }); } +} + +$(document).live('pagebeforehide', function() { + currentPage = ''; + currentUuid = ''; }); +$('#patient').live('pagebeforeshow', RefreshPatient); +$('#study').live('pagebeforeshow', RefreshStudy); +$('#series').live('pagebeforeshow', RefreshSeries); +$('#instance').live('pagebeforeshow', RefreshInstance); + +$(function() { + $(window).hashchange(function(e, data) { + // This fixes the navigation with the back button and with the anonymization + if ('uuid' in $.mobile.pageData && + currentPage == $.mobile.pageData.active && + currentUuid != $.mobile.pageData.uuid) { + if (currentPage == 'patient') + RefreshPatient(); + else if (currentPage == 'study') + RefreshStudy(); + else if (currentPage == 'series') + RefreshSeries(); + else if (currentPage == 'instance') + RefreshInstance(); + } + }); +}); + + + + + function DeleteResource(path) { $.ajax({ @@ -708,6 +789,13 @@ function ChooseDicomModality(callback) { + var clickedModality = ''; + var clickedPeer = ''; + var items = $('<ul>') + .attr('data-divider-theme', 'd') + .attr('data-role', 'listview'); + + // Retrieve the list of the known DICOM modalities $.ajax({ url: '../modalities', type: 'GET', @@ -715,37 +803,66 @@ async: false, cache: false, success: function(modalities) { - var clickedModality = ''; - var items = $('<ul>') - .attr('data-role', 'listview'); + if (modalities.length > 0) + { + items.append('<li data-role="list-divider">DICOM modalities</li>'); - for (var i = 0; i < modalities.length; i++) { - var modality = modalities[i]; - var item = $('<li>') - .html('<a href="#" rel="close">' + modality + '</a>') - .attr('modality', modality) - .click(function() { - clickedModality = $(this).attr('modality'); - }); - items.append(item); + for (var i = 0; i < modalities.length; i++) { + var name = modalities[i]; + var item = $('<li>') + .html('<a href="#" rel="close">' + name + '</a>') + .attr('name', name) + .click(function() { + clickedModality = $(this).attr('name'); + }); + items.append(item); + } } - $('#dialog').simpledialog2({ - mode: 'blank', - animate: false, - headerText: 'DICOM modality', - headerClose: true, - width: '100%', - blankContent: items, - callbackClose: function() { - var timer; - function WaitForDialogToClose() { - if (!$('#dialog').is(':visible')) { - clearInterval(timer); - callback(clickedModality); + // Retrieve the list of the known Orthanc peers + $.ajax({ + url: '../peers', + type: 'GET', + dataType: 'json', + async: false, + cache: false, + success: function(peers) { + if (peers.length > 0) + { + items.append('<li data-role="list-divider">Orthanc peers</li>'); + + for (var i = 0; i < peers.length; i++) { + var name = peers[i]; + var item = $('<li>') + .html('<a href="#" rel="close">' + name + '</a>') + .attr('name', name) + .click(function() { + clickedPeer = $(this).attr('name'); + }); + items.append(item); } } - timer = setInterval(WaitForDialogToClose, 100); + + // Launch the dialog + $('#dialog').simpledialog2({ + mode: 'blank', + animate: false, + headerText: 'Choose target', + headerClose: true, + forceInput: false, + width: '100%', + blankContent: items, + callbackClose: function() { + var timer; + function WaitForDialogToClose() { + if (!$('#dialog').is(':visible')) { + clearInterval(timer); + callback(clickedModality, clickedPeer); + } + } + timer = setInterval(WaitForDialogToClose, 100); + } + }); } }); } @@ -753,29 +870,42 @@ } -$('#instance-store,#series-store').live('click', function(e) { - ChooseDicomModality(function(modality) { - if (modality != '') { +$('#instance-store,#series-store,#study-store,#patient-store').live('click', function(e) { + ChooseDicomModality(function(modality, peer) { + var url; + var loading; + + if (modality != '') + { + url = '../modalities/' + modality + '/store'; + loading = '#dicom-store'; + } + + if (peer != '') + { + url = '../peers/' + peer + '/store'; + loading = '#peer-store'; + } + + if (url != '') { $.ajax({ - url: '../modalities/' + modality + '/store', + url: url, type: 'POST', dataType: 'text', data: $.mobile.pageData.uuid, async: true, // Necessary to block UI beforeSend: function() { - $.blockUI({ message: $('#loading') }); + $.blockUI({ message: $(loading) }); }, complete: function(s) { $.unblockUI(); }, success: function(s) { - //console.log('done !'); }, error: function() { - alert('Error during C-Store'); + alert('Error during store'); } - }); - + }); } }); }); @@ -841,7 +971,7 @@ //$.mobile.changePage('explorer.html#patient?uuid=' + s.PatientID); window.location.assign('explorer.html#patient?uuid=' + s.PatientID); - window.location.reload(); + //window.location.reload(); } }); },
--- a/OrthancExplorer/libs/jqm.page.params.js Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancExplorer/libs/jqm.page.params.js Wed Sep 18 15:27:07 2013 +0200 @@ -105,7 +105,9 @@ // http://stackoverflow.com/a/8295488 $(document).bind("pagebeforechange", function( event, data ) { - $.mobile.pageData = (data && data.options && data.options.pageData) + $.mobile.pageData = (data && data.options && data.options.pageData) ? data.options.pageData : {}; + + $.mobile.pageData.active = data.toPage[0].id; });
--- a/OrthancServer/DatabaseWrapper.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -340,6 +340,15 @@ s.Run(); } + void DatabaseWrapper::DeleteMetadata(int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); + s.BindInt(0, id); + s.BindInt(1, type); + s.Run(); + } + bool DatabaseWrapper::LookupMetadata(std::string& target, int64_t id, MetadataType type) @@ -360,6 +369,23 @@ } } + bool DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?"); + s.BindInt(0, id); + + while (s.Step()) + { + target.push_back(static_cast<MetadataType>(s.ColumnInt(0))); + } + + return true; + } + + std::string DatabaseWrapper::GetMetadata(int64_t id, MetadataType type, const std::string& defaultValue) @@ -412,6 +438,21 @@ s.Run(); } + void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& result, + int64_t id) + { + result.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT fileType FROM AttachedFiles WHERE id=?"); + s.BindInt(0, id); + + while (s.Step()) + { + result.push_back(static_cast<FileContentType>(s.ColumnInt(0))); + } + } + bool DatabaseWrapper::LookupAttachment(FileInfo& attachment, int64_t id, FileContentType contentType) @@ -551,8 +592,8 @@ Json::Value item = Json::objectValue; item["Seq"] = static_cast<int>(seq); - item["ChangeType"] = ToString(changeType); - item["ResourceType"] = ToString(resourceType); + item["ChangeType"] = EnumerationToString(changeType); + item["ResourceType"] = EnumerationToString(resourceType); item["ID"] = publicId; item["Path"] = GetBasePath(resourceType, publicId); item["Date"] = date; @@ -626,7 +667,7 @@ Json::Value item = Json::objectValue; item["Seq"] = static_cast<int>(seq); - item["ResourceType"] = ToString(resourceType); + item["ResourceType"] = EnumerationToString(resourceType); item["ID"] = publicId; item["Path"] = GetBasePath(resourceType, publicId); item["RemoteModality"] = s.ColumnString(3); @@ -902,4 +943,56 @@ return 1; } } + + + void DatabaseWrapper::ClearTable(const std::string& tableName) + { + db_.Execute("DELETE FROM " + tableName); + } + + + bool DatabaseWrapper::IsExistingResource(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM Resources WHERE internalId=?"); + s.BindInt(0, internalId); + return s.Step(); + } + + + void DatabaseWrapper::LookupTagValue(std::list<int64_t>& result, + DicomTag tag, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT id FROM MainDicomTags WHERE tagGroup=? AND tagElement=? and value=?"); + + s.BindInt(0, tag.GetGroup()); + s.BindInt(1, tag.GetElement()); + s.BindString(2, value); + + result.clear(); + + while (s.Step()) + { + result.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapper::LookupTagValue(std::list<int64_t>& result, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT id FROM MainDicomTags WHERE value=?"); + + s.BindString(0, value); + + result.clear(); + + while (s.Step()) + { + result.push_back(s.ColumnInt64(0)); + } + } }
--- a/OrthancServer/DatabaseWrapper.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DatabaseWrapper.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -108,10 +108,16 @@ MetadataType type, const std::string& value); + void DeleteMetadata(int64_t id, + MetadataType type); + bool LookupMetadata(std::string& target, int64_t id, MetadataType type); + bool ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id); + std::string GetMetadata(int64_t id, MetadataType type, const std::string& defaultValue = ""); @@ -123,6 +129,9 @@ void AddAttachment(int64_t id, const FileInfo& attachment); + void ListAvailableAttachments(std::list<FileContentType>& result, + int64_t id); + bool LookupAttachment(FileInfo& attachment, int64_t id, FileContentType contentType); @@ -212,5 +221,16 @@ } uint64_t IncrementGlobalSequence(GlobalProperty property); + + void ClearTable(const std::string& tableName); + + bool IsExistingResource(int64_t internalId); + + void LookupTagValue(std::list<int64_t>& result, + DicomTag tag, + const std::string& value); + + void LookupTagValue(std::list<int64_t>& result, + const std::string& value); }; }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomServer.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -82,6 +82,8 @@ boost::filesystem::path p = directory; p = p / filename; + LOG(WARNING) << "Loading the external DICOM dictionary " << p; + if (!dictionary.loadDictionary(p.string().c_str())) { throw OrthancException(ErrorCode_InternalError); @@ -111,7 +113,7 @@ LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE); #elif defined(__linux) - std::string path = "/usr/share/dcmtk"; + std::string path = DCMTK_DICTIONARY_DIR; const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); if (env != NULL) @@ -385,7 +387,11 @@ void DicomServer::Stop() { continue_ = false; - pimpl_->thread_.join(); + + if (pimpl_->thread_.joinable()) + { + pimpl_->thread_.join(); + } bagOfDispatchers_.StopAll(); }
--- a/OrthancServer/DicomProtocol/DicomServer.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -294,7 +294,20 @@ break; case FindRootModel_Instance: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); + if (manufacturer_ == ModalityManufacturer_ClearCanvas) + { + // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. + // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J + // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx + printf("CLEAR CANVAS\n"); + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + printf("GENERIC\n"); + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; // Accession number @@ -453,6 +466,7 @@ distantAet_ = "ANY-SCP"; distantPort_ = 104; distantHost_ = "127.0.0.1"; + manufacturer_ = ModalityManufacturer_Generic; pimpl_->net_ = NULL; pimpl_->params_ = NULL; @@ -476,6 +490,12 @@ distantAet_ = aet; } + void DicomUserConnection::SetDistantManufacturer(ModalityManufacturer manufacturer) + { + Close(); + manufacturer_ = manufacturer; + } + void DicomUserConnection::SetDistantHost(const std::string& host) {
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,6 +33,7 @@ #pragma once #include "DicomFindAnswers.h" +#include "../ServerEnumerations.h" #include <stdint.h> #include <boost/shared_ptr.hpp> @@ -59,6 +60,7 @@ std::string distantAet_; std::string distantHost_; uint16_t distantPort_; + ModalityManufacturer manufacturer_; void CheckIsOpen() const; @@ -106,6 +108,13 @@ return distantPort_; } + void SetDistantManufacturer(ModalityManufacturer manufacturer); + + ModalityManufacturer GetDistantManufacturer() const + { + return manufacturer_; + } + void Open(); void Close();
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/FromDcmtkBridge.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -29,6 +29,49 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + +Copyright (c) 2006-2011 Mathieu Malaterre +Copyright (c) 1993-2005 CREATIS +(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + #ifndef NOMINMAX #define NOMINMAX #endif @@ -38,7 +81,7 @@ #include "ToDcmtkBridge.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" -#include "../Core/PngWriter.h" +#include "../Core/FileFormats/PngWriter.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomString.h" #include "../Core/DicomFormat/DicomNullValue.h" @@ -1097,9 +1140,9 @@ for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++) { int32_t v = accessor.GetValue(x, y); - if (v < std::numeric_limits<T>::min()) + if (v < static_cast<int32_t>(std::numeric_limits<T>::min())) *pixel = std::numeric_limits<T>::min(); - else if (v > std::numeric_limits<T>::max()) + else if (v > static_cast<int32_t>(std::numeric_limits<T>::max())) *pixel = std::numeric_limits<T>::max(); else *pixel = static_cast<T>(v); @@ -1240,6 +1283,11 @@ accessor.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size())); accessor->SetCurrentFrame(frame); } + + if (accessor.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } PixelFormat format; bool supported = false; @@ -1263,6 +1311,11 @@ format = PixelFormat_Grayscale16; break; + case ImageExtractionMode_Int16: + supported = true; + format = PixelFormat_SignedGrayscale16; + break; + default: supported = false; break; @@ -1314,6 +1367,10 @@ ExtractPngImageTruncate<uint16_t>(result, *accessor, format); break; + case ImageExtractionMode_Int16: + ExtractPngImageTruncate<int16_t>(result, *accessor, format); + break; + default: throw OrthancException(ErrorCode_NotImplemented); }
--- a/OrthancServer/FromDcmtkBridge.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -43,13 +43,6 @@ namespace Orthanc { - enum ImageExtractionMode - { - ImageExtractionMode_Preview, - ImageExtractionMode_UInt8, - ImageExtractionMode_UInt16 - }; - enum DicomRootLevel { DicomRootLevel_Patient,
--- a/OrthancServer/IServerIndexListener.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/IServerIndexListener.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/CommandDispatcher.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -41,9 +41,149 @@ #include <boost/lexical_cast.hpp> #include <glog/logging.h> +#define ORTHANC_PROMISCUOUS 1 + static OFBool opt_rejectWithoutImplementationUID = OFFalse; + +#if ORTHANC_PROMISCUOUS == 1 +static +DUL_PRESENTATIONCONTEXT * +findPresentationContextID(LST_HEAD * head, + T_ASC_PresentationContextID presentationContextID) +{ + DUL_PRESENTATIONCONTEXT *pc; + LST_HEAD **l; + OFBool found = OFFalse; + + if (head == NULL) + return NULL; + + l = &head; + if (*l == NULL) + return NULL; + + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l)); + (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc)); + + while (pc && !found) { + if (pc->presentationContextID == presentationContextID) { + found = OFTrue; + } else { + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l)); + } + } + return pc; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithTransferSyntax( + T_ASC_Parameters * params, + const char* transferSyntax, + T_ASC_SC_ROLE acceptedRole) +{ + OFCondition cond = EC_Normal; + int n, i, k; + DUL_PRESENTATIONCONTEXT *dpc; + T_ASC_PresentationContext pc; + OFBool accepted = OFFalse; + OFBool abstractOK = OFFalse; + + n = ASC_countPresentationContexts(params); + for (i = 0; i < n; i++) + { + cond = ASC_getPresentationContext(params, i, &pc); + if (cond.bad()) return cond; + abstractOK = OFFalse; + accepted = OFFalse; + + if (dcmFindNameOfUID(pc.abstractSyntax) == NULL) + { + abstractOK = OFTrue; + + /* check the transfer syntax */ + for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++) + { + if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0) + { + accepted = OFTrue; + } + } + } + + if (accepted) + { + cond = ASC_acceptPresentationContext( + params, pc.presentationContextID, + transferSyntax, acceptedRole); + if (cond.bad()) return cond; + } else { + T_ASC_P_ResultReason reason; + + /* do not refuse if already accepted */ + dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext, + pc.presentationContextID); + if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))) + { + + if (abstractOK) { + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + } else { + reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED; + } + /* + * If previously this presentation context was refused + * because of bad transfer syntax let it stay that way. + */ + if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED)) + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + + cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason); + if (cond.bad()) return cond; + } + } + } + return EC_Normal; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * This method is passed a list of "preferred" transfer syntaxes. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes( + T_ASC_Parameters * params, + const char* transferSyntaxes[], int transferSyntaxCount, + T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT) +{ + OFCondition cond = EC_Normal; + /* + ** Accept in the order "least wanted" to "most wanted" transfer + ** syntax. Accepting a transfer syntax will override previously + ** accepted transfer syntaxes. + */ + for (int i = transferSyntaxCount - 1; i >= 0; i--) + { + cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole); + if (cond.bad()) return cond; + } + return cond; +} +#endif + + namespace Orthanc { namespace Internals @@ -150,6 +290,18 @@ return NULL; } +#if ORTHANC_PROMISCUOUS == 1 + /* accept everything not known not to be a storage SOP class */ + cond = acceptUnknownContextsWithPreferredTransferSyntaxes( + assoc->params, transferSyntaxes, numTransferSyntaxes); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } +#endif + /* set our app title */ ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
--- a/OrthancServer/Internals/CommandDispatcher.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/FindScp.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/FindScp.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/FindScp.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/FindScp.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/MoveScp.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/MoveScp.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/MoveScp.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/MoveScp.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/StoreScp.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/StoreScp.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -117,7 +117,7 @@ FromDcmtkBridge::Convert(summary, **imageDataSet); FromDcmtkBridge::ToJson(dicomJson, **imageDataSet); - if (FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet) < 0) + if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet)) { LOG(ERROR) << "cannot write DICOM file to memory"; rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
--- a/OrthancServer/Internals/StoreScp.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/Internals/StoreScp.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/OrthancInitialization.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,8 +32,10 @@ #include "OrthancInitialization.h" +#include "../Core/HttpClient.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" +#include "ServerEnumerations.h" #include <boost/lexical_cast.hpp> #include <boost/filesystem.hpp> @@ -47,6 +49,7 @@ static boost::mutex globalMutex_; static std::auto_ptr<Json::Value> configuration_; + static boost::filesystem::path defaultDirectory_; static void ReadGlobalConfiguration(const char* configurationFile) @@ -58,6 +61,7 @@ if (configurationFile) { Toolbox::ReadFile(content, configurationFile); + defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path(); LOG(INFO) << "Using the configuration from: " << configurationFile; } else @@ -116,11 +120,51 @@ } + static void RegisterUserMetadata() + { + if (configuration_->isMember("UserMetadata")) + { + const Json::Value& parameter = (*configuration_) ["UserMetadata"]; + + Json::Value::Members members = parameter.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString(); + LOG(INFO) << "Registering user-defined metadata: " << info; + + if (!parameter[members[i]].asBool()) + { + LOG(ERROR) << "Not a number in this user-defined metadata: " << info; + throw OrthancException(ErrorCode_BadParameterType); + } + + int metadata = parameter[members[i]].asInt(); + + try + { + RegisterUserMetadata(metadata, members[i]); + } + catch (OrthancException e) + { + LOG(ERROR) << "Cannot register this user-defined metadata: " << info; + throw e; + } + } + } + } + + void OrthancInitialize(const char* configurationFile) { boost::mutex::scoped_lock lock(globalMutex_); + + InitializeServerEnumerations(); + defaultDirectory_ = boost::filesystem::current_path(); ReadGlobalConfiguration(configurationFile); - curl_global_init(CURL_GLOBAL_ALL); + + HttpClient::GlobalInitialize(); + + RegisterUserMetadata(); } @@ -128,7 +172,7 @@ void OrthancFinalize() { boost::mutex::scoped_lock lock(globalMutex_); - curl_global_cleanup(); + HttpClient::GlobalFinalize(); configuration_.reset(NULL); } @@ -186,7 +230,8 @@ void GetDicomModality(const std::string& name, std::string& aet, std::string& address, - int& port) + int& port, + ModalityManufacturer& manufacturer) { boost::mutex::scoped_lock lock(globalMutex_); @@ -197,7 +242,8 @@ const Json::Value& modalities = (*configuration_) ["DicomModalities"]; if (modalities.type() != Json::objectValue || - !modalities.isMember(name)) + !modalities.isMember(name) || + (modalities[name].size() != 3 && modalities[name].size() != 4)) { throw OrthancException(""); } @@ -207,6 +253,15 @@ aet = modalities[name].get(0u, "").asString(); address = modalities[name].get(1u, "").asString(); port = modalities[name].get(2u, "").asInt(); + + if (modalities[name].size() == 4) + { + manufacturer = StringToModalityManufacturer(modalities[name].get(3u, "").asString()); + } + else + { + manufacturer = ModalityManufacturer_Generic; + } } catch (...) { @@ -216,36 +271,111 @@ - void GetListOfDicomModalities(std::set<std::string>& target) + void GetOrthancPeer(const std::string& name, + std::string& url, + std::string& username, + std::string& password) + { + boost::mutex::scoped_lock lock(globalMutex_); + + if (!configuration_->isMember("OrthancPeers")) + { + throw OrthancException(""); + } + + const Json::Value& modalities = (*configuration_) ["OrthancPeers"]; + if (modalities.type() != Json::objectValue || + !modalities.isMember(name)) + { + throw OrthancException(""); + } + + try + { + url = modalities[name].get(0u, "").asString(); + + if (modalities[name].size() == 1) + { + username = ""; + password = ""; + } + else if (modalities[name].size() == 3) + { + username = modalities[name].get(1u, "").asString(); + password = modalities[name].get(2u, "").asString(); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + catch (...) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + if (url.size() != 0 && url[url.size() - 1] != '/') + { + url += '/'; + } + } + + + static bool ReadKeys(std::set<std::string>& target, + const char* parameter, + bool onlyAlphanumeric) { boost::mutex::scoped_lock lock(globalMutex_); target.clear(); - if (!configuration_->isMember("DicomModalities")) + if (!configuration_->isMember(parameter)) { - return; + return true; } - const Json::Value& modalities = (*configuration_) ["DicomModalities"]; + const Json::Value& modalities = (*configuration_) [parameter]; if (modalities.type() != Json::objectValue) { - throw OrthancException("Badly formatted list of DICOM modalities"); + throw OrthancException(ErrorCode_BadFileFormat); } Json::Value::Members members = modalities.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { - for (size_t j = 0; j < members[i].size(); j++) + if (onlyAlphanumeric) { - if (!isalnum(members[i][j]) && members[i][j] != '-') + for (size_t j = 0; j < members[i].size(); j++) { - throw OrthancException("Only alphanumeric and dash characters are allowed in the names of the modalities"); + if (!isalnum(members[i][j]) && members[i][j] != '-') + { + return false; + } } } target.insert(members[i]); } + + return true; + } + + + void GetListOfDicomModalities(std::set<std::string>& target) + { + if (!ReadKeys(target, "DicomModalities", true)) + { + throw OrthancException("Only alphanumeric and dash characters are allowed in the names of the modalities"); + } + } + + + void GetListOfOrthancPeers(std::set<std::string>& target) + { + if (!ReadKeys(target, "OrthancPeers", true)) + { + throw OrthancException("Only alphanumeric and dash characters are allowed in the names of Orthanc peers"); + } } @@ -275,4 +405,63 @@ httpServer.RegisterUser(username.c_str(), password.c_str()); } } + + + std::string InterpretRelativePath(const std::string& baseDirectory, + const std::string& relativePath) + { + boost::filesystem::path base(baseDirectory); + boost::filesystem::path relative(relativePath); + + /** + The following lines should be equivalent to this one: + + return (base / relative).string(); + + However, for some unknown reason, some versions of Boost do not + make the proper path resolution when "baseDirectory" is an + absolute path. So, a hack is used below. + **/ + + if (relative.is_absolute()) + { + return relative.string(); + } + else + { + return (base / relative).string(); + } + } + + std::string InterpretStringParameterAsPath(const std::string& parameter) + { + boost::mutex::scoped_lock lock(globalMutex_); + return InterpretRelativePath(defaultDirectory_.string(), parameter); + } + + + void GetGlobalListOfStringsParameter(std::list<std::string>& target, + const std::string& key) + { + boost::mutex::scoped_lock lock(globalMutex_); + + target.clear(); + + if (!configuration_->isMember(key)) + { + return; + } + + const Json::Value& lst = (*configuration_) [key]; + + if (lst.type() != Json::arrayValue) + { + throw OrthancException("Badly formatted list of strings"); + } + + for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++) + { + target.push_back(lst[i].asString()); + } + } }
--- a/OrthancServer/OrthancInitialization.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/OrthancInitialization.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -37,6 +37,7 @@ #include <json/json.h> #include <stdint.h> #include "../Core/HttpServer/MongooseServer.h" +#include "ServerEnumerations.h" namespace Orthanc { @@ -56,9 +57,25 @@ void GetDicomModality(const std::string& name, std::string& aet, std::string& address, - int& port); + int& port, + ModalityManufacturer& manufacturer); + + void GetOrthancPeer(const std::string& name, + std::string& url, + std::string& username, + std::string& password); void GetListOfDicomModalities(std::set<std::string>& target); + void GetListOfOrthancPeers(std::set<std::string>& target); + void SetupRegisteredUsers(MongooseServer& httpServer); + + std::string InterpretRelativePath(const std::string& baseDirectory, + const std::string& relativePath); + + std::string InterpretStringParameterAsPath(const std::string& parameter); + + void GetGlobalListOfStringsParameter(std::list<std::string>& target, + const std::string& key); }
--- a/OrthancServer/OrthancRestApi.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/OrthancRestApi.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,9 +32,10 @@ #include "OrthancRestApi.h" +#include "../Core/Compression/HierarchicalZipWriter.h" +#include "../Core/HttpClient.h" #include "../Core/HttpServer/FilesystemHttpSender.h" #include "../Core/Uuid.h" -#include "../Core/Compression/HierarchicalZipWriter.h" #include "DicomProtocol/DicomUserConnection.h" #include "FromDcmtkBridge.h" #include "OrthancInitialization.h" @@ -52,9 +53,13 @@ ServerContext& context = contextApi.GetContext() #define RETRIEVE_MODALITIES(call) \ - const OrthancRestApi::Modalities& modalities = \ + const OrthancRestApi::SetOfStrings& modalities = \ dynamic_cast<OrthancRestApi&>(call.GetContext()).GetModalities(); +#define RETRIEVE_PEERS(call) \ + const OrthancRestApi::SetOfStrings& peers = \ + dynamic_cast<OrthancRestApi&>(call.GetContext()).GetPeers(); + namespace Orthanc @@ -71,11 +76,13 @@ { std::string aet, address; int port; - GetDicomModality(name, aet, address, port); + ModalityManufacturer manufacturer; + GetDicomModality(name, aet, address, port, manufacturer); connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); connection.SetDistantApplicationEntityTitle(aet); connection.SetDistantHost(address); connection.SetDistantPort(port); + connection.SetDistantManufacturer(manufacturer); connection.Open(); } @@ -174,6 +181,34 @@ call.GetOutput().AnswerJson(result); } + static void DicomFindInstance(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindInstanceTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || + m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModality(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindInstance(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + static void DicomFind(RestApi::PostCall& call) { DicomMap m; @@ -244,50 +279,90 @@ } + static bool GetInstancesToExport(std::list<std::string>& instances, + const std::string& remote, + RestApi::PostCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string stripped = Toolbox::StripSpaces(call.GetPostBody()); + + Json::Value request; + if (Toolbox::IsSHA1(stripped)) + { + // This is for compatibility with Orthanc <= 0.5.1. + request = stripped; + } + else if (!call.ParseJsonRequest(request)) + { + // Bad JSON request + return false; + } + + if (request.isString()) + { + context.GetIndex().LogExportedResource(request.asString(), remote); + context.GetIndex().GetChildInstances(instances, request.asString()); + } + else if (request.isArray()) + { + for (Json::Value::ArrayIndex i = 0; i < request.size(); i++) + { + if (!request[i].isString()) + { + return false; + } + + std::string stripped = Toolbox::StripSpaces(request[i].asString()); + if (!Toolbox::IsSHA1(stripped)) + { + return false; + } + + context.GetIndex().LogExportedResource(stripped, remote); + + std::list<std::string> tmp; + context.GetIndex().GetChildInstances(tmp, stripped); + instances.merge(tmp); + assert(tmp.size() == 0); + } + } + else + { + // Neither a string, nor a list of strings. Bad request. + return false; + } + + return true; + } + + static void DicomStore(RestApi::PostCall& call) { RETRIEVE_CONTEXT(call); std::string remote = call.GetUriComponent("id", ""); + + std::list<std::string> instances; + if (!GetInstancesToExport(instances, remote, call)) + { + return; + } + DicomUserConnection connection; ConnectToModality(connection, remote); - const std::string& resourceId = call.GetPostBody(); - - Json::Value found; - if (context.GetIndex().LookupResource(found, resourceId, ResourceType_Series)) + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); it++) { - // The UUID corresponds to a series - context.GetIndex().LogExportedResource(resourceId, remote); - - for (Json::Value::ArrayIndex i = 0; i < found["Instances"].size(); i++) - { - std::string instanceId = found["Instances"][i].asString(); - std::string dicom; - context.ReadFile(dicom, instanceId, FileContentType_Dicom); - connection.Store(dicom); - } - - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else if (context.GetIndex().LookupResource(found, resourceId, ResourceType_Instance)) - { - // The UUID corresponds to an instance - context.GetIndex().LogExportedResource(resourceId, remote); + LOG(INFO) << "Sending resource " << *it << " to modality \"" << remote << "\""; std::string dicom; - context.ReadFile(dicom, resourceId, FileContentType_Dicom); + context.ReadFile(dicom, *it, FileContentType_Dicom); connection.Store(dicom); - - call.GetOutput().AnswerBuffer("{}", "application/json"); } - else - { - // The POST body is not a known resource, assume that it - // contains a raw DICOM instance - connection.Store(resourceId); - call.GetOutput().AnswerBuffer("{}", "application/json"); - } + + call.GetOutput().AnswerBuffer("{}", "application/json"); } @@ -338,6 +413,23 @@ } } + static void ExecuteScript(RestApi::PostCall& call) + { + std::string result; + RETRIEVE_CONTEXT(call); + context.GetLuaContext().Execute(result, call.GetPostBody()); + call.GetOutput().AnswerBuffer(result, "text/plain"); + } + + static void GetNowIsoString(RestApi::GetCall& call) + { + call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain"); + } + + + + + // List all the patients, studies, series or instances ---------------------- @@ -615,6 +707,14 @@ } + static void DeleteChanges(RestApi::DeleteCall& call) + { + RETRIEVE_CONTEXT(call); + context.GetIndex().DeleteChanges(); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + static void GetExports(RestApi::GetCall& call) { RETRIEVE_CONTEXT(call); @@ -632,6 +732,14 @@ } } + + static void DeleteExports(RestApi::DeleteCall& call) + { + RETRIEVE_CONTEXT(call); + context.GetIndex().DeleteExportedResources(); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + // Get information about a single patient ----------------------------------- @@ -678,6 +786,21 @@ } + static void ExportInstanceFile(RestApi::PostCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string publicId = call.GetUriComponent("id", ""); + + std::string dicom; + context.ReadFile(dicom, publicId, FileContentType_Dicom); + + Toolbox::WriteFile(dicom, call.GetPostBody()); + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + template <bool simplify> static void GetInstanceTags(RestApi::GetCall& call) { @@ -801,7 +924,7 @@ result["Path"] = GetBasePath(ResourceType_Instance, publicId); } - result["Status"] = ToString(status); + result["Status"] = EnumerationToString(status); call.GetOutput().AnswerJson(result); } @@ -809,7 +932,7 @@ // DICOM bridge ------------------------------------------------------------- - static bool IsExistingModality(const OrthancRestApi::Modalities& modalities, + static bool IsExistingModality(const OrthancRestApi::SetOfStrings& modalities, const std::string& id) { return modalities.find(id) != modalities.end(); @@ -820,7 +943,7 @@ RETRIEVE_MODALITIES(call); Json::Value result = Json::arrayValue; - for (OrthancRestApi::Modalities::const_iterator + for (OrthancRestApi::SetOfStrings::const_iterator it = modalities.begin(); it != modalities.end(); it++) { result.append(*it); @@ -841,6 +964,7 @@ result.append("find-patient"); result.append("find-study"); result.append("find-series"); + result.append("find-instance"); result.append("find"); result.append("store"); call.GetOutput().AnswerJson(result); @@ -909,8 +1033,6 @@ throw OrthancException(ErrorCode_BadRequest); } - target.clear(); - for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) { std::string name = removals[i].asString(); @@ -930,8 +1052,6 @@ throw OrthancException(ErrorCode_BadRequest); } - target.clear(); - Json::Value::Members members = replacements.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { @@ -977,7 +1097,7 @@ removals.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID removals.insert(DicomTag(0x0008, 0x2111)); // Derivation Description removals.insert(DicomTag(0x0010, 0x0010)); // Patient's Name - removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID + //removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) removals.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date removals.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time removals.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex @@ -993,8 +1113,8 @@ removals.insert(DicomTag(0x0010, 0x4000)); // Patient Comments removals.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number removals.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => generated below - //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => generated below + //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => cf. below (*) + //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => cf. below (*) removals.insert(DicomTag(0x0020, 0x0010)); // Study ID removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID @@ -1006,29 +1126,25 @@ removals.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID removals.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID + /** + * (*) Patient ID, Study Instance UID and Series Instance UID + * are modified by "AnonymizeInstance()" if anonymizing a single + * instance, or by "RetrieveMappedUid()" if anonymizing a + * patient/study/series. + **/ + + // Some more removals (from the experience of DICOM files at the CHU of Liege) removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers + removals.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts // Set the DeidentificationMethod tag replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); - // Set the PatientIdentityRemoved + // Set the PatientIdentityRemoved tag replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); - - // Generate random study UID if not specified - if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); - } - - // Generate random series UID if not specified - if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); - } } @@ -1131,13 +1247,6 @@ replacements.insert(std::make_pair(DicomTag(0x0010, 0x0010), GeneratePatientName(context))); } - // Generate random Patient's ID if none is specified - if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); - } - return true; } else @@ -1170,14 +1279,23 @@ UidMap& uidMap) { std::auto_ptr<DicomTag> tag; - if (level == DicomRootLevel_Series) + + switch (level) { - tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - } - else - { - assert(level == DicomRootLevel_Study); - tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + case DicomRootLevel_Series: + tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + break; + + case DicomRootLevel_Study: + tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + break; + + case DicomRootLevel_Patient: + tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); } std::string original; @@ -1207,7 +1325,8 @@ } - static void AnonymizeOrModifyResource(Removals& removals, + static void AnonymizeOrModifyResource(bool isAnonymization, + Removals& removals, Replacements& replacements, bool removePrivateTags, MetadataType metadataType, @@ -1244,15 +1363,20 @@ LOG(INFO) << "Modifying instance " << *it; ParsedDicomFile& original = context.GetDicomFile(*it); - bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); + DicomInstanceHasher originalHasher = original.GetHasher(); - bool isNewStudy = false; - if (resourceType == ResourceType_Study || - resourceType == ResourceType_Patient) + if (isFirst && !isAnonymization) { - isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); + // If modifying a study or a series, keep the original patient ID. + std::string patientId = originalHasher.GetPatientId(); + uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId; } + bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); + bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); + bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap); + + /** * Compute the resulting DICOM instance and store it into the Orthanc store. **/ @@ -1269,11 +1393,10 @@ /** - * Record metadata information (AnonimizedFrom/ModifiedFrom). + * Record metadata information (AnonymizedFrom/ModifiedFrom). **/ DicomInstanceHasher modifiedHasher = modified->GetHasher(); - DicomInstanceHasher originalHasher = original.GetHasher(); if (isNewSeries) { @@ -1287,6 +1410,12 @@ metadataType, originalHasher.HashStudy()); } + if (isNewPatient) + { + context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), + metadataType, originalHasher.HashPatient()); + } + assert(*it == originalHasher.HashInstance()); assert(modifiedInstance == modifiedHasher.HashInstance()); context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it); @@ -1318,7 +1447,7 @@ throw OrthancException(ErrorCode_InternalError); } - result["Type"] = ToString(resourceType); + result["Type"] = EnumerationToString(resourceType); result["ID"] = newId; result["Path"] = GetBasePath(resourceType, newId); result["PatientID"] = modifiedHasher.HashPatient(); @@ -1352,6 +1481,27 @@ if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) { + // Generate random patient ID if not specified + if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); + } + + // Generate random study UID if not specified + if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); + } + + // Generate random series UID if not specified + if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); + } + AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); } } @@ -1365,7 +1515,7 @@ if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, + AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, ResourceType_Series, call); } @@ -1380,7 +1530,7 @@ if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, + AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, ResourceType_Series, call); } @@ -1395,7 +1545,7 @@ if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, + AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, ResourceType_Study, call); } @@ -1410,14 +1560,14 @@ if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, + AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, ResourceType_Study, call); } } - static void ModifyPatientInplace(RestApi::PostCall& call) + /*static void ModifyPatientInplace(RestApi::PostCall& call) { Removals removals; Replacements replacements; @@ -1425,11 +1575,11 @@ if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, + AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, ResourceType_Patient, call); } - } + }*/ static void AnonymizePatientInplace(RestApi::PostCall& call) @@ -1440,13 +1590,182 @@ if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, + AnonymizeOrModifyResource(true, removals, replacements, removePrivateTags, MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, ResourceType_Patient, call); } } + // Handling of metadata ----------------------------------------------------- + + static void ListMetadata(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::list<MetadataType> metadata; + if (context.GetIndex().ListAvailableMetadata(metadata, publicId)) + { + Json::Value result = Json::arrayValue; + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); it++) + { + result.append(EnumerationToString(*it)); + } + + call.GetOutput().AnswerJson(result); + } + } + + + static void GetMetadata(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + + std::string value; + if (context.GetIndex().LookupMetadata(value, publicId, metadata)) + { + call.GetOutput().AnswerBuffer(value, "text/plain"); + } + } + + + static void DeleteMetadata(RestApi::DeleteCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + + if (metadata >= MetadataType_StartUser && + metadata <= MetadataType_EndUser) + { + // It is forbidden to modify internal metadata + context.GetIndex().DeleteMetadata(publicId, metadata); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + static void SetMetadata(RestApi::PutCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + std::string value = call.GetPutBody(); + + if (metadata >= MetadataType_StartUser && + metadata <= MetadataType_EndUser) + { + // It is forbidden to modify internal metadata + context.GetIndex().SetMetadata(publicId, metadata, value); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + static void GetResourceStatistics(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + std::string publicId = call.GetUriComponent("id", ""); + Json::Value result; + context.GetIndex().GetStatistics(result, publicId); + call.GetOutput().AnswerJson(result); + } + + + + // Orthanc Peers ------------------------------------------------------------ + + static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers, + const std::string& id) + { + return peers.find(id) != peers.end(); + } + + static void ListPeers(RestApi::GetCall& call) + { + RETRIEVE_PEERS(call); + + Json::Value result = Json::arrayValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = peers.begin(); it != peers.end(); it++) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + static void ListPeerOperations(RestApi::GetCall& call) + { + RETRIEVE_PEERS(call); + + std::string id = call.GetUriComponent("id", ""); + if (IsExistingPeer(peers, id)) + { + Json::Value result = Json::arrayValue; + result.append("store"); + call.GetOutput().AnswerJson(result); + } + } + + static void PeerStore(RestApi::PostCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string remote = call.GetUriComponent("id", ""); + + std::list<std::string> instances; + if (!GetInstancesToExport(instances, remote, call)) + { + return; + } + + std::string url, username, password; + GetOrthancPeer(remote, url, username, password); + + // Configure the HTTP client + HttpClient client; + if (username.size() != 0 && password.size() != 0) + { + client.SetCredentials(username.c_str(), password.c_str()); + } + + client.SetUrl(url + "instances"); + client.SetMethod(HttpMethod_Post); + + // Loop over the instances that are to be sent + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); it++) + { + LOG(INFO) << "Sending resource " << *it << " to peer \"" << remote << "\""; + + context.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom); + + std::string answer; + if (!client.Apply(answer)) + { + LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << remote << "\""; + return; + } + } + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + + // Registration of the various REST handlers -------------------------------- @@ -1454,12 +1773,15 @@ context_(context) { GetListOfDicomModalities(modalities_); + GetListOfOrthancPeers(peers_); Register("/", ServeRoot); Register("/system", GetSystemInformation); Register("/statistics", GetStatistics); Register("/changes", GetChanges); + Register("/changes", DeleteChanges); Register("/exports", GetExports); + Register("/exports", DeleteExports); Register("/instances", UploadDicomFile); Register("/instances", ListResources<ResourceType_Instance>); @@ -1480,9 +1802,32 @@ Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); Register("/series/{id}/archive", GetArchive<ResourceType_Series>); + Register("/instances/{id}/statistics", GetResourceStatistics); + Register("/patients/{id}/statistics", GetResourceStatistics); + Register("/studies/{id}/statistics", GetResourceStatistics); + Register("/series/{id}/statistics", GetResourceStatistics); + + Register("/instances/{id}/metadata", ListMetadata); + Register("/instances/{id}/metadata/{name}", DeleteMetadata); + Register("/instances/{id}/metadata/{name}", GetMetadata); + Register("/instances/{id}/metadata/{name}", SetMetadata); + Register("/patients/{id}/metadata", ListMetadata); + Register("/patients/{id}/metadata/{name}", DeleteMetadata); + Register("/patients/{id}/metadata/{name}", GetMetadata); + Register("/patients/{id}/metadata/{name}", SetMetadata); + Register("/series/{id}/metadata", ListMetadata); + Register("/series/{id}/metadata/{name}", DeleteMetadata); + Register("/series/{id}/metadata/{name}", GetMetadata); + Register("/series/{id}/metadata/{name}", SetMetadata); + Register("/studies/{id}/metadata", ListMetadata); + Register("/studies/{id}/metadata/{name}", DeleteMetadata); + Register("/studies/{id}/metadata/{name}", GetMetadata); + Register("/studies/{id}/metadata/{name}", SetMetadata); + Register("/patients/{id}/protected", IsProtectedPatient); Register("/patients/{id}/protected", SetPatientProtection); Register("/instances/{id}/file", GetInstanceFile); + Register("/instances/{id}/export", ExportInstanceFile); Register("/instances/{id}/tags", GetInstanceTags<false>); Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); Register("/instances/{id}/frames", ListFrames); @@ -1491,22 +1836,29 @@ Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/modalities", ListModalities); Register("/modalities/{id}", ListModalityOperations); Register("/modalities/{id}/find-patient", DicomFindPatient); Register("/modalities/{id}/find-study", DicomFindStudy); Register("/modalities/{id}/find-series", DicomFindSeries); + Register("/modalities/{id}/find-instance", DicomFindInstance); Register("/modalities/{id}/find", DicomFind); Register("/modalities/{id}/store", DicomStore); + Register("/peers", ListPeers); + Register("/peers/{id}", ListPeerOperations); + Register("/peers/{id}/store", PeerStore); + Register("/instances/{id}/modify", ModifyInstance); Register("/series/{id}/modify", ModifySeriesInplace); Register("/studies/{id}/modify", ModifyStudyInplace); - Register("/patients/{id}/modify", ModifyPatientInplace); + //Register("/patients/{id}/modify", ModifyPatientInplace); Register("/instances/{id}/anonymize", AnonymizeInstance); Register("/series/{id}/anonymize", AnonymizeSeriesInplace); @@ -1514,5 +1866,7 @@ Register("/patients/{id}/anonymize", AnonymizePatientInplace); Register("/tools/generate-uid", GenerateUid); + Register("/tools/execute-script", ExecuteScript); + Register("/tools/now", GetNowIsoString); } }
--- a/OrthancServer/OrthancRestApi.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/OrthancRestApi.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -42,11 +42,12 @@ class OrthancRestApi : public RestApi { public: - typedef std::set<std::string> Modalities; + typedef std::set<std::string> SetOfStrings; private: ServerContext& context_; - Modalities modalities_; + SetOfStrings modalities_; + SetOfStrings peers_; public: OrthancRestApi(ServerContext& context); @@ -56,9 +57,14 @@ return context_; } - Modalities& GetModalities() + SetOfStrings& GetModalities() { return modalities_; } + + SetOfStrings& GetPeers() + { + return peers_; + } }; }
--- a/OrthancServer/ServerContext.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerContext.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,11 +33,15 @@ #include "ServerContext.h" #include "../Core/HttpServer/FilesystemHttpSender.h" +#include "../Core/Lua/LuaFunctionCall.h" +#include "ServerToolbox.h" #include <glog/logging.h> +#include <EmbeddedResources.h> #define ENABLE_DICOM_CACHE 1 +static const char* RECEIVED_INSTANCE_FILTER = "ReceivedInstanceFilter"; static const size_t DICOM_CACHE_SIZE = 2; @@ -60,6 +64,7 @@ provider_(*this), dicomCache_(provider_, DICOM_CACHE_SIZE) { + lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); } void ServerContext::SetCompressionEnabled(bool enabled) @@ -83,6 +88,23 @@ const Json::Value& dicomJson, const std::string& remoteAet) { + // Test if the instance must be filtered out + if (lua_.IsExistingFunction(RECEIVED_INSTANCE_FILTER)) + { + Json::Value simplified; + SimplifyTags(simplified, dicomJson); + + LuaFunctionCall call(lua_, RECEIVED_INSTANCE_FILTER); + call.PushJSON(simplified); + call.PushString(remoteAet); + + if (!call.ExecutePredicate()) + { + LOG(INFO) << "An incoming instance has been discarded by the filter"; + return StoreStatus_FilteredOut; + } + } + if (compressionEnabled_) { accessor_.SetCompressionForNextOperations(CompressionType_Zlib); @@ -109,17 +131,21 @@ switch (status) { - case StoreStatus_Success: - LOG(INFO) << "New instance stored"; - break; + case StoreStatus_Success: + LOG(INFO) << "New instance stored"; + break; + + case StoreStatus_AlreadyStored: + LOG(INFO) << "Already stored"; + break; - case StoreStatus_AlreadyStored: - LOG(INFO) << "Already stored"; - break; + case StoreStatus_Failure: + LOG(ERROR) << "Store failure"; + break; - case StoreStatus_Failure: - LOG(ERROR) << "Store failure"; - break; + default: + // This should never happen + break; } return status;
--- a/OrthancServer/ServerContext.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerContext.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -36,6 +36,7 @@ #include "../Core/FileStorage/CompressedFileStorageAccessor.h" #include "../Core/FileStorage/FileStorage.h" #include "../Core/RestApi/RestApiOutput.h" +#include "../Core/Lua/LuaContext.h" #include "ServerIndex.h" #include "FromDcmtkBridge.h" @@ -70,6 +71,8 @@ DicomCacheProvider provider_; MemoryCache dicomCache_; + LuaContext lua_; + public: ServerContext(const boost::filesystem::path& storagePath, const boost::filesystem::path& indexPath); @@ -129,5 +132,10 @@ // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD ParsedDicomFile& GetDicomFile(const std::string& instancePublicId); + + LuaContext& GetLuaContext() + { + return lua_; + } }; }
--- a/OrthancServer/ServerEnumerations.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,10 +32,57 @@ #include "ServerEnumerations.h" #include "../Core/OrthancException.h" +#include "../Core/EnumerationDictionary.h" + +#include <boost/thread.hpp> namespace Orthanc { - const char* ToString(ResourceType type) + static boost::mutex enumerationsMutex_; + static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_; + + void InitializeServerEnumerations() + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + + dictMetadataType_.Add(MetadataType_Instance_IndexInSeries, "IndexInSeries"); + dictMetadataType_.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate"); + dictMetadataType_.Add(MetadataType_Instance_RemoteAet, "RemoteAET"); + dictMetadataType_.Add(MetadataType_Series_ExpectedNumberOfInstances, "ExpectedNumberOfInstances"); + dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); + dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom"); + dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate"); + } + + void RegisterUserMetadata(int metadata, + const std::string name) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + + if (metadata < static_cast<int>(MetadataType_StartUser) || + metadata > static_cast<int>(MetadataType_EndUser)) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + dictMetadataType_.Add(static_cast<MetadataType>(metadata), name); + } + + std::string EnumerationToString(MetadataType type) + { + // This function MUST return a "std::string" and not "const + // char*", as the result is not a static string + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictMetadataType_.Translate(type); + } + + MetadataType StringToMetadata(const std::string& str) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictMetadataType_.Translate(str); + } + + const char* EnumerationToString(ResourceType type) { switch (type) { @@ -78,7 +125,7 @@ } } - const char* ToString(SeriesStatus status) + const char* EnumerationToString(SeriesStatus status) { switch (status) { @@ -99,7 +146,7 @@ } } - const char* ToString(StoreStatus status) + const char* EnumerationToString(StoreStatus status) { switch (status) { @@ -112,13 +159,16 @@ case StoreStatus_Failure: return "Failure"; + case StoreStatus_FilteredOut: + return "FilteredOut"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } } - const char* ToString(ChangeType type) + const char* EnumerationToString(ChangeType type) { switch (type) { @@ -155,6 +205,15 @@ case ChangeType_ModifiedPatient: return "ModifiedPatient"; + case ChangeType_StablePatient: + return "StablePatient"; + + case ChangeType_StableStudy: + return "StableStudy"; + + case ChangeType_StableSeries: + return "StableSeries"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -197,4 +256,39 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + const char* EnumerationToString(ModalityManufacturer manufacturer) + { + switch (manufacturer) + { + case ModalityManufacturer_Generic: + return "Generic"; + + case ModalityManufacturer_ClearCanvas: + return "ClearCanvas"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer) + { + if (manufacturer == "Generic") + { + return ModalityManufacturer_Generic; + } + else if (manufacturer == "ClearCanvas") + { + return ModalityManufacturer_ClearCanvas; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + }
--- a/OrthancServer/ServerEnumerations.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerEnumerations.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -47,7 +47,14 @@ { StoreStatus_Success, StoreStatus_AlreadyStored, - StoreStatus_Failure + StoreStatus_Failure, + StoreStatus_FilteredOut // Removed by NewInstanceFilter + }; + + enum ModalityManufacturer + { + ModalityManufacturer_Generic, + ModalityManufacturer_ClearCanvas }; @@ -79,7 +86,12 @@ MetadataType_Instance_RemoteAet = 3, MetadataType_Series_ExpectedNumberOfInstances = 4, MetadataType_ModifiedFrom = 5, - MetadataType_AnonymizedFrom = 6 + MetadataType_AnonymizedFrom = 6, + MetadataType_LastUpdate = 7, + + // Make sure that the value "65535" can be stored into this enumeration + MetadataType_StartUser = 1024, + MetadataType_EndUser = 65535 }; enum ChangeType @@ -94,19 +106,35 @@ ChangeType_ModifiedStudy = 8, ChangeType_ModifiedSeries = 9, ChangeType_AnonymizedPatient = 10, - ChangeType_ModifiedPatient = 11 + ChangeType_ModifiedPatient = 11, + ChangeType_StablePatient = 12, + ChangeType_StableStudy = 13, + ChangeType_StableSeries = 14 }; + void InitializeServerEnumerations(); + + void RegisterUserMetadata(int metadata, + const std::string name); + std::string GetBasePath(ResourceType type, const std::string& publicId); - const char* ToString(ResourceType type); + MetadataType StringToMetadata(const std::string& str); + + const char* EnumerationToString(ResourceType type); - const char* ToString(SeriesStatus status); + std::string EnumerationToString(MetadataType type); + + const char* EnumerationToString(SeriesStatus status); - const char* ToString(StoreStatus status); + const char* EnumerationToString(StoreStatus status); + + const char* EnumerationToString(ChangeType type); - const char* ToString(ChangeType type); + const char* EnumerationToString(ModalityManufacturer manufacturer); + + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); ResourceType GetParentResourceType(ResourceType type);
--- a/OrthancServer/ServerIndex.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerIndex.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -37,6 +37,7 @@ #endif #include "EmbeddedResources.h" +#include "OrthancInitialization.h" #include "../Core/Toolbox.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomArray.h" @@ -48,6 +49,8 @@ #include <stdio.h> #include <glog/logging.h> +static const uint64_t MEGA_BYTES = 1024 * 1024; + namespace Orthanc { namespace Internals @@ -185,6 +188,27 @@ }; + struct ServerIndex::UnstableResourcePayload + { + Orthanc::ResourceType type_; + boost::posix_time::ptime time_; + + UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance) + { + } + + UnstableResourcePayload(Orthanc::ResourceType type) : type_(type) + { + time_ = boost::posix_time::second_clock::local_time(); + } + + unsigned int GetAge() const + { + return (boost::posix_time::second_clock::local_time() - time_).total_seconds(); + } + }; + + bool ServerIndex::DeleteResource(Json::Value& target, const std::string& uuid, ResourceType expectedType) @@ -211,7 +235,7 @@ target["RemainingAncestor"] = Json::Value(Json::objectValue); target["RemainingAncestor"]["Path"] = GetBasePath(type, uuid); - target["RemainingAncestor"]["Type"] = ToString(type); + target["RemainingAncestor"]["Type"] = EnumerationToString(type); target["RemainingAncestor"]["ID"] = uuid; } else @@ -225,23 +249,86 @@ } - static void FlushThread(DatabaseWrapper* db, - boost::mutex* mutex, - unsigned int sleep) + void ServerIndex::FlushThread(ServerIndex* that) { + unsigned int sleep; + + try + { + std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep); + sleep = boost::lexical_cast<unsigned int>(sleepString); + } + catch (boost::bad_lexical_cast&) + { + // By default, wait for 10 seconds before flushing + sleep = 10; + } + LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")"; - while (1) + unsigned int count = 0; + + while (!that->done_) { - boost::this_thread::sleep(boost::posix_time::seconds(sleep)); - boost::mutex::scoped_lock lock(*mutex); - db->FlushToDisk(); + boost::this_thread::sleep(boost::posix_time::seconds(1)); + count++; + if (count < sleep) + { + continue; + } + + boost::mutex::scoped_lock lock(that->mutex_); + that->db_->FlushToDisk(); + count = 0; + } + + LOG(INFO) << "Stopping the database flushing thread"; + } + + + static void ComputeExpectedNumberOfInstances(DatabaseWrapper& db, + int64_t series, + const DicomMap& dicomSummary) + { + const DicomValue* value; + const DicomValue* value2; + + try + { + if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL && + (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL) + { + // Patch for series with temporal positions thanks to Will Ryder + int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->AsString()); + int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->AsString()); + std::string expected = boost::lexical_cast<std::string>(imagesInAcquisition * countTemporalPositions); + db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); + } + + else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL && + (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL) + { + // Support of Cardio-PET images + int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->AsString()); + int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->AsString()); + std::string expected = boost::lexical_cast<std::string>(numberOfSlices * numberOfTimeSlices); + db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); + } + + else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL) + { + db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString()); + } + } + catch (boost::bad_lexical_cast) + { } } ServerIndex::ServerIndex(ServerContext& context, const std::string& dbPath) : + done_(false), maximumStorageSize_(0), maximumPatients_(0) { @@ -272,27 +359,24 @@ // execution of Orthanc StandaloneRecycling(); - unsigned int sleep; - try - { - std::string sleepString = db_->GetGlobalProperty(GlobalProperty_FlushSleep); - sleep = boost::lexical_cast<unsigned int>(sleepString); - } - catch (boost::bad_lexical_cast&) - { - // By default, wait for 10 seconds before flushing - sleep = 10; - } - - flushThread_ = boost::thread(FlushThread, db_.get(), &mutex_, sleep); + flushThread_ = boost::thread(FlushThread, this); + unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this); } ServerIndex::~ServerIndex() { - LOG(INFO) << "Stopping the database flushing thread"; - /*flushThread_.terminate(); - flushThread_.join();*/ + done_ = true; + + if (flushThread_.joinable()) + { + flushThread_.join(); + } + + if (unstableResourcesMonitorThread_.joinable()) + { + unstableResourcesMonitorThread_.join(); + } } @@ -309,15 +393,15 @@ { Transaction t(*this); - int64_t patient, study, series, instance; - ResourceType type; - bool isNewSeries = false; - // Do nothing if the instance already exists - if (db_->LookupResource(hasher.HashInstance(), patient, type)) { - assert(type == ResourceType_Instance); - return StoreStatus_AlreadyStored; + ResourceType type; + int64_t tmp; + if (db_->LookupResource(hasher.HashInstance(), tmp, type)) + { + assert(type == ResourceType_Instance); + return StoreStatus_AlreadyStored; + } } // Ensure there is enough room in the storage for the new instance @@ -331,56 +415,101 @@ Recycle(instanceSize, hasher.HashPatient()); // Create the instance - instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance); + int64_t instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance); DicomMap dicom; dicomSummary.ExtractInstanceInformation(dicom); db_->SetMainDicomTags(instance, dicom); - // Create the patient/study/series/instance hierarchy - if (!db_->LookupResource(hasher.HashSeries(), series, type)) + // Detect up to which level the patient/study/series/instance + // hierarchy must be created + int64_t patient = -1, study = -1, series = -1; + bool isNewPatient = false; + bool isNewStudy = false; + bool isNewSeries = false; + { - // This is a new series - isNewSeries = true; + ResourceType dummy; + + if (db_->LookupResource(hasher.HashSeries(), series, dummy)) + { + assert(dummy == ResourceType_Series); + // The patient, the study and the series already exist + + bool ok = (db_->LookupResource(hasher.HashPatient(), patient, dummy) && + db_->LookupResource(hasher.HashStudy(), study, dummy)); + assert(ok); + } + else if (db_->LookupResource(hasher.HashStudy(), study, dummy)) + { + assert(dummy == ResourceType_Study); + + // New series: The patient and the study already exist + isNewSeries = true; + + bool ok = db_->LookupResource(hasher.HashPatient(), patient, dummy); + assert(ok); + } + else if (db_->LookupResource(hasher.HashPatient(), patient, dummy)) + { + assert(dummy == ResourceType_Patient); + + // New study and series: The patient already exist + isNewStudy = true; + isNewSeries = true; + } + else + { + // New patient, study and series: Nothing exists + isNewPatient = true; + isNewStudy = true; + isNewSeries = true; + } + } + + // Create the series if needed + if (isNewSeries) + { series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series); dicomSummary.ExtractSeriesInformation(dicom); db_->SetMainDicomTags(series, dicom); - db_->AttachChild(series, instance); + } - if (!db_->LookupResource(hasher.HashStudy(), study, type)) - { - // This is a new study - study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study); - dicomSummary.ExtractStudyInformation(dicom); - db_->SetMainDicomTags(study, dicom); - db_->AttachChild(study, series); + // Create the study if needed + if (isNewStudy) + { + study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study); + dicomSummary.ExtractStudyInformation(dicom); + db_->SetMainDicomTags(study, dicom); + } + + // Create the patient if needed + if (isNewPatient) + { + patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient); + dicomSummary.ExtractPatientInformation(dicom); + db_->SetMainDicomTags(patient, dicom); + } - if (!db_->LookupResource(hasher.HashPatient(), patient, type)) - { - // This is a new patient - patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient); - dicomSummary.ExtractPatientInformation(dicom); - db_->SetMainDicomTags(patient, dicom); - db_->AttachChild(patient, study); - } - else - { - assert(type == ResourceType_Patient); - db_->AttachChild(patient, study); - } - } - else - { - assert(type == ResourceType_Study); - db_->AttachChild(study, series); - } + // Create the parent-to-child links + db_->AttachChild(series, instance); + + if (isNewSeries) + { + db_->AttachChild(study, series); } - else + + if (isNewStudy) { - assert(type == ResourceType_Series); - db_->AttachChild(series, instance); + db_->AttachChild(patient, study); } + // Sanity checks + assert(patient != -1); + assert(study != -1); + assert(series != -1); + assert(instance != -1); + // Attach the files to the newly created instance for (Attachments::const_iterator it = attachments.begin(); it != attachments.end(); it++) @@ -389,7 +518,11 @@ } // Attach the metadata - db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, Toolbox::GetNowIsoString()); + std::string now = Toolbox::GetNowIsoString(); + db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now); + db_->SetMetadata(series, MetadataType_LastUpdate, now); + db_->SetMetadata(study, MetadataType_LastUpdate, now); + db_->SetMetadata(patient, MetadataType_LastUpdate, now); db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet); const DicomValue* value; @@ -401,12 +534,7 @@ if (isNewSeries) { - if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL || - (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL || - (value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL) - { - db_->SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString()); - } + ComputeExpectedNumberOfInstances(*db_, series, dicomSummary); } // Check whether the series of this new instance is now completed @@ -416,6 +544,11 @@ db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series); } + // Mark the parent resources of this instance as unstable + MarkAsUnstable(patient, ResourceType_Patient); + MarkAsUnstable(study, ResourceType_Study); + MarkAsUnstable(series, ResourceType_Series); + t.Commit(instanceSize); return StoreStatus_Success; @@ -432,18 +565,16 @@ void ServerIndex::ComputeStatistics(Json::Value& target) { - static const uint64_t MB = 1024 * 1024; - boost::mutex::scoped_lock lock(mutex_); target = Json::objectValue; uint64_t cs = currentStorageSize_; assert(cs == db_->GetTotalCompressedSize()); uint64_t us = db_->GetTotalUncompressedSize(); - target["TotalDiskSpace"] = boost::lexical_cast<std::string>(cs); + target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs); target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us); - target["TotalDiskSpaceMB"] = boost::lexical_cast<unsigned int>(cs / MB); - target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MB); + target["TotalDiskSizeMB"] = boost::lexical_cast<unsigned int>(cs / MEGA_BYTES); + target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MEGA_BYTES); target["CountPatients"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Patient)); target["CountStudies"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Study)); @@ -622,7 +753,7 @@ case ResourceType_Series: { result["Type"] = "Series"; - result["Status"] = ToString(GetSeriesStatus(id)); + result["Status"] = EnumerationToString(GetSeriesStatus(id)); int i; if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances)) @@ -673,6 +804,13 @@ if (tmp.size() != 0) result["ModifiedFrom"] = tmp; + if (type == ResourceType_Patient || + type == ResourceType_Study || + type == ResourceType_Series) + { + result["IsStable"] = !unstableResources_.Contains(id); + } + return true; } @@ -918,7 +1056,7 @@ } else { - LOG(WARNING) << "At most " << (size / (1024 * 1024)) << "MB will be used for the storage area"; + LOG(WARNING) << "At most " << (size / MEGA_BYTES) << "MB will be used for the storage area"; } StandaloneRecycling(); @@ -1040,6 +1178,23 @@ db_->SetMetadata(id, type, value); } + + void ServerIndex::DeleteMetadata(const std::string& publicId, + MetadataType type) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + db_->DeleteMetadata(id, type); + } + + bool ServerIndex::LookupMetadata(std::string& target, const std::string& publicId, MetadataType type) @@ -1057,6 +1212,22 @@ } + bool ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + return db_->ListAvailableMetadata(target, id); + } + + bool ServerIndex::LookupParent(std::string& target, const std::string& publicId) { @@ -1115,4 +1286,224 @@ transaction->Commit(); } + + + void ServerIndex::DeleteChanges() + { + boost::mutex::scoped_lock lock(mutex_); + db_->ClearTable("Changes"); + } + + void ServerIndex::DeleteExportedResources() + { + boost::mutex::scoped_lock lock(mutex_); + db_->ClearTable("ExportedResources"); + } + + + void ServerIndex::GetStatistics(Json::Value& target, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t top; + if (!db_->LookupResource(publicId, top, type)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + std::stack<int64_t> toExplore; + toExplore.push(top); + + int countInstances = 0; + int countSeries = 0; + int countStudies = 0; + uint64_t compressedSize = 0; + uint64_t uncompressedSize = 0; + + while (!toExplore.empty()) + { + // Get the internal ID of the current resource + int64_t resource = toExplore.top(); + toExplore.pop(); + + ResourceType thisType = db_->GetResourceType(resource); + + if (thisType == ResourceType_Instance) + { + std::list<FileContentType> f; + db_->ListAvailableAttachments(f, resource); + + for (std::list<FileContentType>::const_iterator + it = f.begin(); it != f.end(); it++) + { + FileInfo attachment; + if (db_->LookupAttachment(attachment, resource, *it)) + { + compressedSize += attachment.GetCompressedSize(); + uncompressedSize += attachment.GetUncompressedSize(); + } + } + + countInstances++; + } + else + { + switch (thisType) + { + case ResourceType_Study: + countStudies++; + break; + + case ResourceType_Series: + countSeries++; + break; + + default: + break; + } + + // Tag all the children of this resource as to be explored + std::list<int64_t> tmp; + db_->GetChildrenInternalId(tmp, resource); + for (std::list<int64_t>::const_iterator + it = tmp.begin(); it != tmp.end(); it++) + { + toExplore.push(*it); + } + } + } + + target = Json::objectValue; + target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize); + target["DiskSizeMB"] = boost::lexical_cast<unsigned int>(compressedSize / MEGA_BYTES); + target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize); + target["UncompressedSizeMB"] = boost::lexical_cast<unsigned int>(uncompressedSize / MEGA_BYTES); + + switch (type) + { + // Do NOT add "break" below this point! + case ResourceType_Patient: + target["CountStudies"] = countStudies; + + case ResourceType_Study: + target["CountSeries"] = countSeries; + + case ResourceType_Series: + target["CountInstances"] = countInstances; + + case ResourceType_Instance: + default: + break; + } + } + + + void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that) + { + int stableAge = GetGlobalIntegerParameter("StableAge", 60); + if (stableAge <= 0) + { + stableAge = 60; + } + + LOG(INFO) << "Starting the monitor for stable resources (stable age = " << stableAge << ")"; + + while (!that->done_) + { + // Check for stable resources each second + boost::this_thread::sleep(boost::posix_time::seconds(1)); + + boost::mutex::scoped_lock lock(that->mutex_); + + while (!that->unstableResources_.IsEmpty() && + that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge)) + { + // This DICOM resource has not received any new instance for + // some time. It can be considered as stable. + + UnstableResourcePayload payload; + int64_t id = that->unstableResources_.RemoveOldest(payload); + + // Ensure that the resource is still existing before logging the change + if (that->db_->IsExistingResource(id)) + { + switch (payload.type_) + { + case Orthanc::ResourceType_Patient: + that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient); + break; + + case Orthanc::ResourceType_Study: + that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study); + break; + + case Orthanc::ResourceType_Series: + that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + //LOG(INFO) << "Stable resource: " << EnumerationToString(payload.type_) << " " << id; + } + } + } + + LOG(INFO) << "Closing the monitor thread for stable resources"; + } + + + void ServerIndex::MarkAsUnstable(int64_t id, + Orthanc::ResourceType type) + { + // WARNING: Before calling this method, "mutex_" must be locked. + + assert(type == Orthanc::ResourceType_Patient || + type == Orthanc::ResourceType_Study || + type == Orthanc::ResourceType_Series); + + unstableResources_.AddOrMakeMostRecent(id, type); + //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id; + } + + + + void ServerIndex::LookupTagValue(std::list<std::string>& result, + DicomTag tag, + const std::string& value) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + std::list<int64_t> id; + db_->LookupTagValue(id, tag, value); + + for (std::list<int64_t>::const_iterator + it = id.begin(); it != id.end(); it++) + { + result.push_back(db_->GetPublicId(*it)); + } + } + + + void ServerIndex::LookupTagValue(std::list<std::string>& result, + const std::string& value) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + std::list<int64_t> id; + db_->LookupTagValue(id, value); + + for (std::list<int64_t>::const_iterator + it = id.begin(); it != id.end(); it++) + { + result.push_back(db_->GetPublicId(*it)); + } + } }
--- a/OrthancServer/ServerIndex.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerIndex.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -34,6 +34,7 @@ #include <boost/thread.hpp> #include <boost/noncopyable.hpp> +#include "../Core/Cache/LeastRecentlyUsedIndex.h" #include "../Core/SQLite/Connection.h" #include "../Core/DicomFormat/DicomMap.h" #include "../Core/DicomFormat/DicomInstanceHasher.h" @@ -55,17 +56,25 @@ { private: class Transaction; + struct UnstableResourcePayload; + bool done_; boost::mutex mutex_; boost::thread flushThread_; + boost::thread unstableResourcesMonitorThread_; std::auto_ptr<Internals::ServerIndexListener> listener_; std::auto_ptr<DatabaseWrapper> db_; + LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload> unstableResources_; uint64_t currentStorageSize_; uint64_t maximumStorageSize_; unsigned int maximumPatients_; + static void FlushThread(ServerIndex* that); + + static void UnstableResourcesMonitorThread(ServerIndex* that); + void MainDicomTagsToJson(Json::Value& result, int64_t resourceId); @@ -78,6 +87,9 @@ void StandaloneRecycling(); + void MarkAsUnstable(int64_t id, + Orthanc::ResourceType type); + public: typedef std::list<FileInfo> Attachments; @@ -150,10 +162,16 @@ MetadataType type, const std::string& value); + void DeleteMetadata(const std::string& publicId, + MetadataType type); + bool LookupMetadata(std::string& target, const std::string& publicId, MetadataType type); + bool ListAvailableMetadata(std::list<MetadataType>& target, + const std::string& publicId); + bool LookupParent(std::string& target, const std::string& publicId); @@ -161,5 +179,19 @@ void LogChange(ChangeType changeType, const std::string& publicId); + + void DeleteChanges(); + + void DeleteExportedResources(); + + void GetStatistics(Json::Value& target, + const std::string& publicId); + + void LookupTagValue(std::list<std::string>& result, + DicomTag tag, + const std::string& value); + + void LookupTagValue(std::list<std::string>& result, + const std::string& value); }; }
--- a/OrthancServer/ServerToolbox.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerToolbox.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/ServerToolbox.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ServerToolbox.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/ToDcmtkBridge.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ToDcmtkBridge.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/ToDcmtkBridge.h Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/ToDcmtkBridge.h Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/main.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/OrthancServer/main.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -38,7 +38,7 @@ #include "../Core/HttpServer/EmbeddedResourceHttpHandler.h" #include "../Core/HttpServer/FilesystemHttpHandler.h" -#include "../Core/HttpServer/MongooseServer.h" +#include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" #include "DicomProtocol/DicomServer.h" #include "OrthancInitialization.h" @@ -51,11 +51,11 @@ class MyStoreRequestHandler : public IStoreRequestHandler { private: - ServerContext& context_; + ServerContext& server_; public: MyStoreRequestHandler(ServerContext& context) : - context_(context) + server_(context) { } @@ -66,7 +66,7 @@ { if (dicomFile.size() > 0) { - context_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet); + server_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet); } } }; @@ -148,6 +148,66 @@ }; +class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter +{ +private: + ServerContext& context_; + +public: + MyIncomingHttpRequestFilter(ServerContext& context) : context_(context) + { + } + + virtual bool IsAllowed(HttpMethod method, + const char* uri, + const char* ip, + const char* username) const + { + static const char* HTTP_FILTER = "IncomingHttpRequestFilter"; + + // Test if the instance must be filtered out + if (context_.GetLuaContext().IsExistingFunction(HTTP_FILTER)) + { + LuaFunctionCall call(context_.GetLuaContext(), HTTP_FILTER); + + switch (method) + { + case HttpMethod_Get: + call.PushString("GET"); + break; + + case HttpMethod_Put: + call.PushString("PUT"); + break; + + case HttpMethod_Post: + call.PushString("POST"); + break; + + case HttpMethod_Delete: + call.PushString("DELETE"); + break; + + default: + return true; + } + + call.PushString(uri); + call.PushString(ip); + call.PushString(username); + + if (!call.ExecutePredicate()) + { + LOG(INFO) << "An incoming HTTP request has been discarded by the filter"; + return false; + } + } + + return true; + } +}; + + void PrintHelp(char* path) { std::cout @@ -264,8 +324,10 @@ OrthancInitialize(); } - boost::filesystem::path storageDirectory = GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); - boost::filesystem::path indexDirectory = GetGlobalStringParameter("IndexDirectory", storageDirectory.string()); + std::string storageDirectoryStr = GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); + boost::filesystem::path storageDirectory = InterpretStringParameterAsPath(storageDirectoryStr); + boost::filesystem::path indexDirectory = + InterpretStringParameterAsPath(GetGlobalStringParameter("IndexDirectory", storageDirectoryStr)); ServerContext context(storageDirectory, indexDirectory); LOG(WARNING) << "Storage directory: " << storageDirectory; @@ -273,6 +335,19 @@ context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false)); + std::list<std::string> luaScripts; + GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); + for (std::list<std::string>::const_iterator + it = luaScripts.begin(); it != luaScripts.end(); it++) + { + std::string path = InterpretStringParameterAsPath(*it); + LOG(WARNING) << "Installing the Lua scripts from: " << path; + std::string script; + Toolbox::ReadFile(script, path); + context.GetLuaContext().Execute(script); + } + + try { context.GetIndex().SetMaximumPatientCount(GetGlobalIntegerParameter("MaximumPatientCount", 0)); @@ -306,16 +381,19 @@ dicomServer.SetApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); // HTTP server + MyIncomingHttpRequestFilter httpFilter(context); MongooseServer httpServer; httpServer.SetPortNumber(GetGlobalIntegerParameter("HttpPort", 8042)); httpServer.SetRemoteAccessAllowed(GetGlobalBoolParameter("RemoteAccessAllowed", false)); + httpServer.SetIncomingHttpRequestFilter(httpFilter); httpServer.SetAuthenticationEnabled(GetGlobalBoolParameter("AuthenticationEnabled", false)); SetupRegisteredUsers(httpServer); if (GetGlobalBoolParameter("SslEnabled", false)) { - std::string certificate = GetGlobalStringParameter("SslCertificate", "certificate.pem"); + std::string certificate = + InterpretStringParameterAsPath(GetGlobalStringParameter("SslCertificate", "certificate.pem")); httpServer.SetSslEnabled(true); httpServer.SetSslCertificate(certificate.c_str()); }
--- a/README Mon Apr 29 12:48:10 2013 +0200 +++ b/README Wed Sep 18 15:27:07 2013 +0200 @@ -41,12 +41,18 @@ exception: http://people.gnome.org/~markmc/openssl-and-the-gpl.html -Because Orthanc uses the Software-as-a-Service paradigm, commercial -products are allowed to access the Orthanc REST services and to bundle -Orthanc in an commercial aggregate. +We also kindly require scientific works and clinical studies that make +use of Orthanc to cite Orthanc in their associated +publications. Similarly, we require open-source and closed-source +products that make use of Orthanc to warn us about this use. You can +cite our work using the following BibTeX entry: -We also kindly require scientific works and clinical studies that make -use of Orthanc to cite Orthanc in their associated publications. +@inproceedings{Jodogne:ISBI2013, + author = "Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.", + title = "Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research", + booktitle = "Proc. of the International Symposium on Biomedical Imaging ({ISBI})", + year = "2013", +} Licensing of special directories @@ -54,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. @@ -68,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
--- a/Resources/CMake/BoostConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/BoostConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -39,9 +39,17 @@ if (BOOST_STATIC) - SET(BOOST_NAME boost_1_49_0) + # Parameters for Boost 1.54.0 + SET(BOOST_NAME boost_1_54_0) + SET(BOOST_MD5 "cee688c35a9c7775b7305587e782e3f5") + SET(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") + SET(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) - DownloadPackage("http://switch.dl.sourceforge.net/project/boost/boost/1.49.0/${BOOST_NAME}.tar.gz" "${BOOST_SOURCES_DIR}" "${BOOST_PRELOADED}" "${BOOST_NAME}/boost ${BOOST_NAME}/libs/thread/src ${BOOST_NAME}/libs/system/src ${BOOST_NAME}/libs/filesystem/v3/src ${BOOST_NAME}/libs/locale/src ${BOOST_NAME}/libs/date_time/src") + DownloadPackage( + "${BOOST_MD5}" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_bcpdigest.tar.gz" + "${BOOST_SOURCES_DIR}" + ) set(BOOST_SOURCES) if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") @@ -62,7 +70,7 @@ ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/windows_file_codecvt.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp ) add_definitions( -DBOOST_LOCALE_WITH_WCONV=1 @@ -73,10 +81,10 @@ list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/codecvt_error_category.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/operations.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/path.cpp - ${BOOST_SOURCES_DIR}/libs/filesystem/v3/src/path_traits.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/BoostConfiguration.sh Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e +set -u + +## Starting with version 0.6.2, Orthanc is shipped with a subset of the +## Boost libraries that is generated with the BCP tool: +## +## http://www.boost.org/doc/libs/1_54_0/tools/bcp/doc/html/index.html +## +## This script generates this subset. + +rm -rf /tmp/boost_1_54_0 +rm -rf /tmp/bcp/boost_1_54_0 + +cd /tmp +echo "Uncompressing the source of Boost 1.54.0..." +tar xfz boost_1_54_0.tar.gz + +echo "Generating the subset..." +mkdir -p /tmp/bcp/boost_1_54_0 +bcp --boost=/tmp/boost_1_54_0 thread system locale date_time filesystem math/special_functions algorithm /tmp/bcp/boost_1_54_0 +cd /tmp/bcp + +echo "Compressing the subset..." +tar cfz boost_1_54_0_bcpdigest.tar.gz boost_1_54_0 +ls -l boost_1_54_0_bcpdigest.tar.gz +md5sum boost_1_54_0_bcpdigest.tar.gz
--- a/Resources/CMake/Compiler.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/Compiler.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -29,28 +29,6 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - if (DEBIAN_FORCE_HARDENING) - execute_process( - COMMAND dpkg-buildflags --get CPPFLAGS - OUTPUT_VARIABLE DEBIAN_CPP_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process( - COMMAND dpkg-buildflags --get CFLAGS - OUTPUT_VARIABLE DEBIAN_C_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process( - COMMAND dpkg-buildflags --get CXXFLAGS - OUTPUT_VARIABLE DEBIAN_CXX_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process( - COMMAND dpkg-buildflags --get LDFLAGS - OUTPUT_VARIABLE DEBIAN_LD_FLAGS - OUTPUT_STRIP_TRAILING_WHITESPACE) - - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEBIAN_C_FLAGS} ${DEBIAN_CPP_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBIAN_CXX_FLAGS} ${DEBIAN_CPP_FLAGS}") - endif() - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive") endif() @@ -59,9 +37,9 @@ -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64 ) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed ${DEBIAN_LD_FLAGS}") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined ${DEBIAN_LD_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") # Remove the "-rdynamic" option # http://www.mail-archive.com/cmake@cmake.org/msg08837.html @@ -74,6 +52,12 @@ -D_CRT_SECURE_NO_WARNINGS=1 ) link_libraries(rpcrt4 ws2_32) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") + endif() + endif()
--- a/Resources/CMake/DcmtkConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -1,7 +1,12 @@ +add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") + if (${STATIC_BUILD}) SET(DCMTK_VERSION_NUMBER 360) SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - DownloadPackage("ftp://dicom.offis.de/pub/dicom/offis/software/dcmtk/dcmtk360/dcmtk-3.6.0.zip" "${DCMTK_SOURCES_DIR}" "" "") + DownloadPackage( + "219ad631b82031806147e4abbfba4fa4" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" + "${DCMTK_SOURCES_DIR}") IF(CMAKE_CROSSCOMPILING) SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") @@ -42,6 +47,15 @@ list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + execute_process( + COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + endif() + endif() list(REMOVE_ITEM DCMTK_SOURCES
--- a/Resources/CMake/DownloadPackage.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/DownloadPackage.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -10,20 +10,16 @@ endmacro() -macro(DownloadPackage Url TargetDirectory PreloadedVariable UncompressArguments) +macro(DownloadPackage MD5 Url TargetDirectory) if (NOT IS_DIRECTORY "${TargetDirectory}") GetUrlFilename(TMP_FILENAME "${Url}") - if ("${PreloadedVariable}" STREQUAL "") - set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") - if (NOT EXISTS "${TMP_PATH}") - message("Downloading ${Url}") - file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS) - else() - message("Using local copy of ${Url}") - endif() + + set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") + if (NOT EXISTS "${TMP_PATH}") + message("Downloading ${Url}") + file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}") else() - message("Using preloaded archive ${PreloadedVariable} for ${Url}") - set(TMP_PATH "${PreloadedVariable}") + message("Using local copy of ${Url}") endif() GetUrlExtension(TMP_EXTENSION "${Url}") @@ -48,37 +44,18 @@ message(FATAL_ERROR "Error while running the uncompression tool") endif() - set(ARGS ${UncompressArguments}) - SEPARATE_ARGUMENTS(ARGS) - list(LENGTH ARGS TMP_LENGTH) - if ("${TMP_EXTENSION}" STREQUAL "tgz") string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") else() string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") endif() - if (TMP_LENGTH EQUAL 0) - execute_process( - COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - else() - foreach(SUBDIR ${ARGS}) - execute_process( - COMMAND ${ZIP_EXECUTABLE} x -y "-i!${SUBDIR}" "${TMP_FILENAME2}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - - if (Failure) - message(FATAL_ERROR "Error while running the uncompression tool") - endif() - endforeach() - endif() + execute_process( + COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) elseif ("${TMP_EXTENSION}" STREQUAL "zip") execute_process( COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH} @@ -93,20 +70,20 @@ else() if ("${TMP_EXTENSION}" STREQUAL "zip") execute_process( - COMMAND sh -c "unzip -q ${TMP_PATH} ${UncompressArguments}" + COMMAND sh -c "unzip -q ${TMP_PATH}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) - #message("tar xvfz ${TMP_PATH} ${UncompressArguments}") + #message("tar xvfz ${TMP_PATH}") execute_process( - COMMAND sh -c "tar xfz ${TMP_PATH} ${UncompressArguments}" + COMMAND sh -c "tar xfz ${TMP_PATH}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) elseif ("${TMP_EXTENSION}" STREQUAL "bz2") execute_process( - COMMAND sh -c "tar xfj ${TMP_PATH} ${UncompressArguments}" + COMMAND sh -c "tar xfj ${TMP_PATH}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) @@ -124,4 +101,3 @@ endif() endif() endmacro() -
--- a/Resources/CMake/GoogleLogConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/GoogleLogConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,9 @@ if (STATIC_BUILD OR NOT USE_DYNAMIC_GOOGLE_LOG) SET(GOOGLE_LOG_SOURCES_DIR ${CMAKE_BINARY_DIR}/glog-0.3.2) - DownloadPackage("http://google-glog.googlecode.com/files/glog-0.3.2.tar.gz" "${GOOGLE_LOG_SOURCES_DIR}" "" "") + DownloadPackage( + "897fbff90d91ea2b6d6e78c8cea641cc" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.2.tar.gz" + "${GOOGLE_LOG_SOURCES_DIR}") set(GOOGLE_LOG_HEADERS ${GOOGLE_LOG_SOURCES_DIR}/src/glog/logging.h @@ -94,8 +97,16 @@ -DNO_FRAME_POINTER=1 -DGOOGLE_GLOG_DLL_DECL= ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + add_definitions(-D_TIME_H__S=1) + endif() + endif() + + add_library(GoogleLog STATIC ${GOOGLE_LOG_SOURCES}) link_libraries(GoogleLog)
--- a/Resources/CMake/GoogleTestConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/GoogleTestConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -9,7 +9,10 @@ elseif (STATIC_BUILD OR NOT USE_DYNAMIC_GOOGLE_TEST) SET(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.6.0) - DownloadPackage("http://googletest.googlecode.com/files/gtest-1.6.0.zip" "${GTEST_SOURCES_DIR}" "" "") + DownloadPackage( + "4577b49f2973c90bf9ba69aa8166b786" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.6.0.zip" + "${GTEST_SOURCES_DIR}") include_directories( ${GTEST_SOURCES_DIR}/include
--- a/Resources/CMake/JsonCppConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/JsonCppConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -8,8 +8,11 @@ link_libraries(jsoncpp) else() - SET(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.5.0) - DownloadPackage("http://downloads.sourceforge.net/project/jsoncpp/jsoncpp/0.5.0/jsoncpp-src-0.5.0.tar.gz" "${JSONCPP_SOURCES_DIR}" "" "") + SET(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2) + DownloadPackage( + "363e2f4cbd3aeb63bf4e571f377400fb" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz" + "${JSONCPP_SOURCES_DIR}") list(APPEND THIRD_PARTY_SOURCES ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
--- a/Resources/CMake/LibCurlConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/LibCurlConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,9 @@ if (${STATIC_BUILD}) SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.26.0) - DownloadPackage("http://curl.haxx.se/download/curl-7.26.0.tar.gz" "${CURL_SOURCES_DIR}" "" "") + DownloadPackage( + "3fa4d5236f2a36ca5c3af6715e837691" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.26.0.tar.gz" + "${CURL_SOURCES_DIR}") include_directories(${CURL_SOURCES_DIR}/include) AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES)
--- a/Resources/CMake/LibPngConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/LibPngConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,9 @@ if (${STATIC_BUILD}) SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) - DownloadPackage("http://download.sourceforge.net/libpng/libpng-1.5.12.tar.gz" "${LIBPNG_SOURCES_DIR}" "${LIBPNG_PRELOADED}" "") + DownloadPackage( + "8ea7f60347a306c5faf70b977fa80e28" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz" + "${LIBPNG_SOURCES_DIR}") include_directories( ${LIBPNG_SOURCES_DIR}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LuaConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,67 @@ +if (STATIC_BUILD OR NOT USE_DYNAMIC_LUA) + SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5) + DownloadPackage( + "2e115fe26e435e33b0d5c022e4490567" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz" + "${LUA_SOURCES_DIR}") + + add_definitions( + #-DLUA_LIB=1 + #-Dluaall_c=1 + #-DLUA_COMPAT_ALL=1 # Compile a generic version of Lua + ) + + include_directories( + ${LUA_SOURCES_DIR}/src + ) + + set(LUA_SOURCES + # Core Lua + ${LUA_SOURCES_DIR}/src/lapi.c + ${LUA_SOURCES_DIR}/src/lcode.c + ${LUA_SOURCES_DIR}/src/ldebug.c + ${LUA_SOURCES_DIR}/src/ldo.c + ${LUA_SOURCES_DIR}/src/ldump.c + ${LUA_SOURCES_DIR}/src/lfunc.c + ${LUA_SOURCES_DIR}/src/lgc.c + ${LUA_SOURCES_DIR}/src/llex.c + ${LUA_SOURCES_DIR}/src/lmem.c + ${LUA_SOURCES_DIR}/src/lobject.c + ${LUA_SOURCES_DIR}/src/lopcodes.c + ${LUA_SOURCES_DIR}/src/lparser.c + ${LUA_SOURCES_DIR}/src/lstate.c + ${LUA_SOURCES_DIR}/src/lstring.c + ${LUA_SOURCES_DIR}/src/ltable.c + ${LUA_SOURCES_DIR}/src/ltm.c + ${LUA_SOURCES_DIR}/src/lundump.c + ${LUA_SOURCES_DIR}/src/lvm.c + ${LUA_SOURCES_DIR}/src/lzio.c + + # Base Lua modules + ${LUA_SOURCES_DIR}/src/lauxlib.c + ${LUA_SOURCES_DIR}/src/lbaselib.c + ${LUA_SOURCES_DIR}/src/ldblib.c + ${LUA_SOURCES_DIR}/src/liolib.c + ${LUA_SOURCES_DIR}/src/lmathlib.c + ${LUA_SOURCES_DIR}/src/loslib.c + ${LUA_SOURCES_DIR}/src/ltablib.c + ${LUA_SOURCES_DIR}/src/lstrlib.c + ${LUA_SOURCES_DIR}/src/loadlib.c + ${LUA_SOURCES_DIR}/src/linit.c + ) + + add_library(Lua STATIC ${LUA_SOURCES}) + link_libraries(Lua) + + source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*) + +else() + include(FindLua51) + + if (NOT LUA51_FOUND) + message(FATAL_ERROR "Please install the liblua-dev package") + endif() + + include_directories(${LUA_INCLUDE_DIR}) + link_libraries(${LUA_LIBRARIES}) +endif()
--- a/Resources/CMake/MongooseConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/MongooseConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,9 @@ if (STATIC_BUILD OR NOT USE_DYNAMIC_MONGOOSE) SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose) - DownloadPackage("http://mongoose.googlecode.com/files/mongoose-3.1.tgz" "${MONGOOSE_SOURCES_DIR}" "" "") + DownloadPackage( + "e718fc287b4eb1bd523be3fa00942bb0" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz" + "${MONGOOSE_SOURCES_DIR}") # Patch mongoose execute_process( @@ -31,6 +34,14 @@ ) endif() + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + add_definitions(-D_TIMESPEC_DEFINED=1) + endif() + endif() + source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*) else()
--- a/Resources/CMake/OpenSslConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/OpenSslConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,9 @@ if (${STATIC_BUILD}) SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.1c) - DownloadPackage("http://www.openssl.org/source/openssl-1.0.1c.tar.gz" "${OPENSSL_SOURCES_DIR}" "" "") + DownloadPackage( + "ae412727c8c15b67880aef7bd2999b2e" + "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.1c.tar.gz" + "${OPENSSL_SOURCES_DIR}") if (NOT EXISTS "${OPENSSL_SOURCES_DIR}/include/PATCHED") if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
--- a/Resources/CMake/SQLiteConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/SQLiteConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,9 @@ if (STATIC_BUILD OR NOT USE_DYNAMIC_SQLITE) SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300) - DownloadPackage("http://www.sqlite.org/sqlite-amalgamation-3071300.zip" "${SQLITE_SOURCES_DIR}" "" "") + DownloadPackage( + "5fbeff9645ab035a1f580e90b279a16d" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip" + "${SQLITE_SOURCES_DIR}") list(APPEND THIRD_PARTY_SOURCES ${SQLITE_SOURCES_DIR}/sqlite3.c @@ -18,6 +21,7 @@ ) source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*) + else() CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H) if (NOT HAVE_SQLITE_H)
--- a/Resources/CMake/ZlibConfiguration.cmake Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/CMake/ZlibConfiguration.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -6,7 +6,10 @@ if (${STATIC_BUILD}) SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7) - DownloadPackage("http://zlib.net/zlib-1.2.7.tar.gz" "${ZLIB_SOURCES_DIR}" "${ZLIB_PRELOADED}" "") + DownloadPackage( + "60df6a37c56e7c1366cca812414f7b85" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz" + "${ZLIB_SOURCES_DIR}") include_directories( ${ZLIB_SOURCES_DIR}
--- a/Resources/Configuration.json Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/Configuration.json Wed Sep 18 15:27:07 2013 +0200 @@ -1,98 +1,136 @@ { - /** - * General configuration of Orthanc - **/ + /** + * General configuration of Orthanc + **/ - // The logical name of this instance of Orthanc. This one is - // displayed in Orthanc Explorer and at the URI "/system". - "Name" : "MyOrthanc", + // The logical name of this instance of Orthanc. This one is + // displayed in Orthanc Explorer and at the URI "/system". + "Name" : "MyOrthanc", - // Path to the directory that holds the heavyweight files - // (i.e. the raw DICOM instances) - "StorageDirectory" : "OrthancStorage", + // Path to the directory that holds the heavyweight files + // (i.e. the raw DICOM instances) + "StorageDirectory" : "OrthancStorage", - // Path to the directory that holds the SQLite index (if unset, - // the value of StorageDirectory is used). This index could be - // stored on a RAM-drive or a SSD device for performance reasons. - "IndexDirectory" : "OrthancStorage", + // Path to the directory that holds the SQLite index (if unset, + // the value of StorageDirectory is used). This index could be + // stored on a RAM-drive or a SSD device for performance reasons. + "IndexDirectory" : "OrthancStorage", - // Enable the transparent compression of the DICOM instances - "StorageCompression" : false, + // Enable the transparent compression of the DICOM instances + "StorageCompression" : false, - // Maximum size of the storage in MB (a value of "0" indicates no - // limit on the storage size) - "MaximumStorageSize" : 0, + // Maximum size of the storage in MB (a value of "0" indicates no + // limit on the storage size) + "MaximumStorageSize" : 0, - // Maximum number of patients that can be stored at a given time - // in the storage (a value of "0" indicates no limit on the number - // of patients) - "MaximumPatientCount" : 0, + // Maximum number of patients that can be stored at a given time + // in the storage (a value of "0" indicates no limit on the number + // of patients) + "MaximumPatientCount" : 0, + + // List of paths to the custom Lua scripts to load into this + // instance of Orthanc + "LuaScripts" : [ + ], - /** - * Configuration of the HTTP server - **/ + + /** + * Configuration of the HTTP server + **/ + + // HTTP port for the REST services and for the GUI + "HttpPort" : 8042, + + - // HTTP port for the REST services and for the GUI - "HttpPort" : 8042, + /** + * Configuration of the DICOM server + **/ + + // The DICOM Application Entity Title + "DicomAet" : "ORTHANC", + + // Check whether the called AET corresponds during a DICOM request + "DicomCheckCalledAet" : false, + + // The DICOM port + "DicomPort" : 4242, - /** - * Configuration of the DICOM server - **/ + /** + * Security-related options for the HTTP server + **/ + + // Whether remote hosts can connect to the HTTP server + "RemoteAccessAllowed" : false, + + // Whether or not SSL is enabled + "SslEnabled" : false, - // The DICOM Application Entity Title - "DicomAet" : "ORTHANC", + // Path to the SSL certificate (meaningful only if SSL is enabled) + "SslCertificate" : "certificate.pem", + + // Whether or not the password protection is enabled + "AuthenticationEnabled" : false, - // Check whether the called AET corresponds during a DICOM request - "DicomCheckCalledAet" : false, - - // The DICOM port - "DicomPort" : 4242, + // The list of the registered users. Because Orthanc uses HTTP + // Basic Authentication, the passwords are stored as plain text. + "RegisteredUsers" : { + // "alice" : "alicePassword" + }, - /** - * Security-related options for the HTTP server - **/ + /** + * Network topology + **/ - // Whether remote hosts can connect to the HTTP server - "RemoteAccessAllowed" : false, - - // Whether or not SSL is enabled - "SslEnabled" : false, + // The list of the known DICOM modalities + "DicomModalities" : { + /** + * Uncommenting the following line would enable Orthanc to + * connect to an instance of the "storescp" open-source DICOM + * store (shipped in the DCMTK distribution) started by the + * command line "storescp 2000". + **/ + // "sample" : [ "STORESCP", "localhost", 2000 ] - // Path to the SSL certificate (meaningful only if SSL is enabled) - "SslCertificate" : "certificate.pem", - - // Whether or not the password protection is enabled - "AuthenticationEnabled" : false, + /** + * A fourth parameter is available to enable patches for a + * specific PACS manufacturer. The allowed values are currently + * "Generic" (default value) and "ClearCanvas". This parameter is + * case-sensitive. + **/ + // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] + }, - // The list of the registered users. Because Orthanc uses HTTP - // Basic Authentication, the passwords are stored as plain text. - "RegisteredUsers" : { - "alice" : "alicePassword" - }, + // The list of the known Orthanc peers + "OrthancPeers" : { + /** + * Each line gives the base URL of an Orthanc peer, possibly + * followed by the username/password pair (if the password + * protection is enabled on the peer). + **/ + // "peer" : [ "http://localhost:8043/", "alice", "alicePassword" ] + // "peer2" : [ "http://localhost:8044/" ] + }, - /** - * Network topology - **/ + /** + * Advanced options + **/ - // The list of the known DICOM modalities - "DicomModalities" : { - /** - * Uncommenting the following line would enable Orthanc to - * connect to an instance of the "storescp" open-source DICOM - * store (shipped in the DCMTK distribution) started by the - * command line "storescp 2000". - **/ - // "sample" : [ "STORESCP", "localhost", 2000 ] - }, + // Dictionary of symbolic names for the user-defined metadata. Each + // entry must map a number between 1024 and 65535 to an unique + // string. + "UserMetadata" : { + // "Sample" : 1024 + }, - // The list of the known Orthanc peers (currently unused) - "OrthancPeers" : { - } + // Number of seconds without receiving any instance before a + // patient, a study or a series is considered as stable. + "StableAge" : 60 }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/EclipseCodingStyle.xml Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<profiles version="1"> +<profile kind="CodeFormatterProfile" name="Orthanc" version="1"> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.lineSplit" value="80"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_member_access" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_constructor_initializer_list" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.comment.min_distance_between_code_and_line_comment" value="1"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.tabulation.size" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_enumerator_list" value="49"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_declarator_list" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_empty_lines" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_method_declaration" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.join_wrapped_lines" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.comment.never_indent_line_comments_on_first_column" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_type_declaration" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.continuation_indentation" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_expression_list" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_binary_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression" value="34"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_extra_spaces" value="0"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_compact_if" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_assignment" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression_chain" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_binary_operator" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration" value="80"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_body" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_binary_expression" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_block" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.tabulation.char" value="space"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_colon_in_constructor_initializer_list" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block_in_case" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.compact_else_if" value="true"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_switch" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.alignment_for_overloaded_left_shift_chain" value="18"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line" value="false"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.indentation.size" value="2"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.brace_position_for_array_initializer" value="next_line"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket" value="do not insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters" value="insert"/> +<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments" value="do not insert"/> +</profile> +</profiles>
--- a/Resources/EmbedResources.py Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/EmbedResources.py Wed Sep 18 15:27:07 2013 +0200 @@ -1,5 +1,5 @@ # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012 Medical Physics Department, CHU of Liege, +# Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, # Belgium # # This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-mingw64.patch Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,22 @@ +diff -urEb dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h +--- dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h 2010-12-17 11:50:30.000000000 +0100 ++++ dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h 2013-07-19 15:56:25.688996134 +0200 +@@ -196,7 +196,7 @@ + OFBool popen(const char *command, const char *modes) + { + if (file_) fclose(); +-#ifdef _WIN32 ++#if 0 + file_ = _popen(command, modes); + #else + file_ = :: popen(command, modes); +@@ -258,7 +258,7 @@ + { + if (popened_) + { +-#ifdef _WIN32 ++#if 0 + result = _pclose(file_); + #else + result = :: pclose(file_); +Only in dcmtk-3.6.0/ofstd/include/dcmtk/ofstd: offile.h~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-mingw64.txt Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,1 @@ +diff -urEb dcmtk-3.6.0.orig/ dcmtk-3.6.0 > ../Resources/Patches/dcmtk-mingw64.patch
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancCppClient/Basic/CMakeLists.txt Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,16 @@ +# Mini-project to check whether "OrthancCppClient" can compile in a +# standalone fashion + +cmake_minimum_required(VERSION 2.8) + +project(OrthancCppClientTest) + +set(STATIC_BUILD OFF) +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../../..) + +include(../OrthancCppClient.cmake) + +add_executable(Test + main.cpp + ${ORTHANC_CPP_CLIENT_SOURCES} + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancCppClient/Basic/main.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +#include <iostream> + +#include "../../../../Core/HttpClient.h" +#include "../../../../OrthancCppClient/OrthancConnection.h" + +int main() +{ + // Prepare a simple call to a Web service + Orthanc::HttpClient c; + c.SetUrl("http://nominatim.openstreetmap.org/search?format=json&q=chu+liege+belgium"); + + // Do the request and store the result in a JSON structure + Json::Value result; + c.Apply(result); + + // Display the JSON answer + std::cout << result << std::endl; + + // Display the content of the local Orthanc instance + OrthancClient::OrthancConnection orthanc("http://localhost:8042"); + + for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++) + { + OrthancClient::Patient& patient = orthanc.GetPatient(i); + std::cout << "Patient: " << patient.GetId() << std::endl; + + for (unsigned int j = 0; j < patient.GetStudyCount(); j++) + { + OrthancClient::Study& study = patient.GetStudy(j); + std::cout << " Study: " << study.GetId() << std::endl; + + for (unsigned int k = 0; k < study.GetSeriesCount(); k++) + { + OrthancClient::Series& series = study.GetSeries(k); + std::cout << " Series: " << series.GetId() << std::endl; + + for (unsigned int l = 0; l < series.GetInstanceCount(); l++) + { + std::cout << " Instance: " << series.GetInstance(l).GetId() << std::endl; + } + } + } + } + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancCppClient/OrthancCppClient.cmake Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,34 @@ +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibCurlConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/LibPngConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) + +if (${CMAKE_COMPILER_IS_GNUCXX}) + set(CMAKE_C_FLAGS "-Wall -pedantic -Wno-implicit-function-declaration") # --std=c99 makes libcurl not to compile + set(CMAKE_CXX_FLAGS "-Wall -pedantic -Wno-long-long -Wno-variadic-macros") + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed") + set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") +elseif (${MSVC}) + add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) +endif() + +set(ORTHANC_CPP_CLIENT_SOURCES + ${THIRD_PARTY_SOURCES} + ${ORTHANC_ROOT}/Core/OrthancException.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/Core/HttpClient.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/ArrayFilledByThreads.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/ThreadedCommandProcessor.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp + ${ORTHANC_ROOT}/Core/FileFormats/PngReader.cpp + ${ORTHANC_ROOT}/OrthancCppClient/OrthancConnection.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Series.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Study.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Instance.cpp + ${ORTHANC_ROOT}/OrthancCppClient/Patient.cpp + ${ORTHANC_ROOT}/Resources/sha1/sha1.cpp + ${ORTHANC_ROOT}/Resources/md5/md5.c + ${ORTHANC_ROOT}/Resources/base64/base64.cpp + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancCppClient/Vtk/CMakeLists.txt Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.8) + +project(OrthancCppClientTest) + +set(STATIC_BUILD OFF) +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../../..) + +include(../OrthancCppClient.cmake) + +find_package(VTK REQUIRED) +include(${VTK_USE_FILE}) + +add_executable(Test + main.cpp + ${ORTHANC_CPP_CLIENT_SOURCES} + ) + +if(VTK_LIBRARIES) + target_link_libraries(Test ${VTK_LIBRARIES}) +else() + target_link_libraries(Test vtkHybrid vtkVolumeRendering) +endif() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancCppClient/Vtk/main.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,196 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +#include <iostream> + +#include <vtkRenderWindow.h> +#include <vtkImageData.h> +#include <vtkPiecewiseFunction.h> +#include <vtkFixedPointVolumeRayCastMapper.h> +#include <vtkColorTransferFunction.h> +#include <vtkVolumeProperty.h> +#include <vtkRenderWindowInteractor.h> +#include <vtkRenderer.h> +#include <vtkSmartPointer.h> +#include <vtkOpenGLRenderer.h> +#include <vtkInteractorStyleTrackballCamera.h> + +#include "../../../../OrthancCppClient/OrthancConnection.h" + + +class DisplayProgress : public Orthanc::ThreadedCommandProcessor::IListener +{ +public: + virtual void SignalProgress(unsigned int current, + unsigned int total) + { + std::cout << "Slice loaded (" << current << "/" << total << ")" << std::endl; + } + + virtual void SignalSuccess(unsigned int total) + { + std::cout << "Success loading image (" << total << " images)" << std::endl; + } + + virtual void SignalCancel() + { + } + + virtual void SignalFailure() + { + std::cout << "Error loading image" << std::endl; + } +}; + + + +void Display(OrthancClient::Series& series) +{ + /** + * Load the 3D image from Orthanc into VTK. + **/ + + vtkSmartPointer<vtkImageData> image = vtkSmartPointer<vtkImageData>::New(); + image->SetDimensions(series.GetWidth(), series.GetHeight(), series.GetInstanceCount()); + image->SetScalarType(VTK_SHORT); + image->AllocateScalars(); + + if (series.GetWidth() != 0 && + series.GetHeight() != 0 && + series.GetInstanceCount() != 0) + { + DisplayProgress listener; + series.Load3DImage(image->GetScalarPointer(0, 0, 0), Orthanc::PixelFormat_SignedGrayscale16, + 2 * series.GetWidth(), 2 * series.GetHeight() * series.GetWidth(), listener); + } + + float sx, sy, sz; + series.GetVoxelSize(sx, sy, sz); + image->SetSpacing(sx, sy, sz); + + + /** + * The following code is based on the VTK sample for MIP + * http://www.vtk.org/Wiki/VTK/Examples/Cxx/VolumeRendering/MinIntensityRendering + **/ + + // Create a transfer function mapping scalar value to opacity + double range[2]; + image->GetScalarRange(range); + + vtkSmartPointer<vtkPiecewiseFunction> opacityTransfer = + vtkSmartPointer<vtkPiecewiseFunction>::New(); + opacityTransfer->AddSegment(range[0], 0.0, range[1], 1.0); + + vtkSmartPointer<vtkColorTransferFunction> colorTransfer = + vtkSmartPointer<vtkColorTransferFunction>::New(); + colorTransfer->AddRGBPoint(0, 1.0, 1.0, 1.0); + colorTransfer->AddRGBPoint(range[1], 1.0, 1.0, 1.0); + + vtkSmartPointer<vtkVolumeProperty> property = + vtkSmartPointer<vtkVolumeProperty>::New(); + property->SetScalarOpacity(opacityTransfer); + property->SetColor(colorTransfer); + property->SetInterpolationTypeToLinear(); + + // Create a Maximum Intensity Projection rendering + vtkSmartPointer<vtkFixedPointVolumeRayCastMapper> mapper = + vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New(); + mapper->SetBlendModeToMaximumIntensity(); + mapper->SetInput(image); + + vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New(); + volume->SetMapper(mapper); + volume->SetProperty(property); + + vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkOpenGLRenderer>::New(); + renderer->AddViewProp(volume); + renderer->SetBackground(0.1, 0.2, 0.3); // Background color dark blue + + vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = + vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New(); + + vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New(); + window->AddRenderer(renderer); + + vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); + interactor->SetRenderWindow(window); + interactor->SetInteractorStyle(style); + interactor->Start(); +} + + +int main() +{ + // Use the commented code below if you know the identifier of a + // series that corresponds to a 3D image. + + /* + { + OrthancClient::OrthancConnection orthanc("http://localhost:8042"); + OrthancClient::Series series(orthanc, "c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5"); + Display(series); + return 0; + } + */ + + + // Try and find a 3D image inside the local store + OrthancClient::OrthancConnection orthanc("http://localhost:8042"); + + for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++) + { + OrthancClient::Patient& patient = orthanc.GetPatient(i); + std::cout << "Patient: " << patient.GetId() << std::endl; + + for (unsigned int j = 0; j < patient.GetStudyCount(); j++) + { + OrthancClient::Study& study = patient.GetStudy(j); + std::cout << " Study: " << study.GetId() << std::endl; + + for (unsigned int k = 0; k < study.GetSeriesCount(); k++) + { + OrthancClient::Series& series = study.GetSeries(k); + std::cout << " Series: " << series.GetId() << std::endl; + + if (series.Is3DImage()) + { + Display(series); + return 0; + } + else + { + std::cout << " => Not a 3D image..." << std::endl; + } + } + } + } + + std::cout << "Unable to find a 3D image in the local Orthanc store" << std::endl; + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,142 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + + +URL = 'http://localhost:8042' +TARGET = 'sample' + + +# +# This sample code shows how to setup a simple, high-performance DICOM +# auto-routing. All the DICOM instances that arrive inside Orthanc +# will be sent to a remote modality. A producer-consumer pattern is +# used. The target modality is specified by the TARGET variable above: +# It must match an entry in the Orthanc configuration file inside the +# "DicomModalities" section. +# +# NOTE: This sample only works with Orthanc >= 0.5.2. Make sure that +# Orthanc was built with "-DCMAKE_BUILD_TYPE=Release" to get the best +# performance. +# + +import Queue +import sys +import time +import threading + +import RestToolbox + + +# +# Queue that is shared between the producer and the consumer +# threads. It holds the instances that are still to be sent. +# + +queue = Queue.Queue() + + +# +# The producer thread. It monitors the arrival of new instances into +# Orthanc, and pushes their ID into the shared queue. This code is +# based upon the "ChangesLoop.py" sample code. +# + +def Producer(queue): + current = 0 + + while True: + r = RestToolbox.DoGet(URL + '/changes', { + 'since' : current, + 'limit' : 4 # Retrieve at most 4 changes at once + }) + + for change in r['Changes']: + # We are only interested interested in the arrival of new instances + if change['ChangeType'] == 'NewInstance': + queue.put(change['ID']) + + current = r['Last'] + + if r['Done']: + time.sleep(1) + + +# +# The consumer thread. It continuously reads the instances from the +# queue, and send them to the remote modality. Each time a packet of +# instances is sent, a single DICOM connexion is used, hence improving +# the performance. +# + +def Consumer(queue): + TIMEOUT = 0.1 + + while True: + instances = [] + + while True: + try: + # Block for a while, waiting for the arrival of a new + # instance + instance = queue.get(True, TIMEOUT) + + # A new instance has arrived: Record its ID + instances.append(instance) + queue.task_done() + + except Queue.Empty: + # Timeout: No more data was received + break + + if len(instances) > 0: + print 'Sending a packet of %d instances' % len(instances) + start = time.time() + + # Send all the instances with a single DICOM connexion + RestToolbox.DoPost('%s/modalities/sample/store' % URL, instances) + + # Remove all the instances from Orthanc + for instance in instances: + RestToolbox.DoDelete('%s/instances/%s' % (URL, instance)) + + # Clear the log of the exported instances (to prevent the + # SQLite database from growing indefinitely) + RestToolbox.DoDelete('%s/exports' % URL) + + end = time.time() + print 'The packet of %d instances has been sent in %d seconds' % (len(instances), end - start) + + +# +# Thread to display the progress +# + +def PrintProgress(queue): + while True: + print 'Current queue size: %d' % (queue.qsize()) + time.sleep(1) + + +# +# Start the various threads +# + +progress = threading.Thread(None, PrintProgress, None, (queue, )) +progress.daemon = True +progress.start() + +producer = threading.Thread(None, Producer, None, (queue, )) +producer.daemon = True +producer.start() + +consumer = threading.Thread(None, Consumer, None, (queue, )) +consumer.daemon = True +consumer.start() + + +# +# Active waiting for Ctrl-C +# + +while True: + time.sleep(0.1)
--- a/Resources/Samples/Python/RestToolbox.py Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/Samples/Python/RestToolbox.py Wed Sep 18 15:27:07 2013 +0200 @@ -13,7 +13,7 @@ if _credentials != None: h.add_credentials(_credentials[0], _credentials[1]) -def DoGet(uri, data = {}): +def DoGet(uri, data = {}, interpretAsJson = True): d = '' if len(data.keys()) > 0: d = '?' + urlencode(data) @@ -23,6 +23,8 @@ resp, content = h.request(uri + d, 'GET') if not (resp.status in [ 200 ]): raise Exception(resp.status) + elif not interpretAsJson: + return content else: try: return json.loads(content)
--- a/Resources/Samples/RestApi/CMakeLists.txt Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/Samples/RestApi/CMakeLists.txt Wed Sep 18 15:27:07 2013 +0200 @@ -22,8 +22,8 @@ UPDATE_COMMAND "" SOURCE_DIR ${CMAKE_BINARY_DIR}/Orthanc/src/orthanc/ CMAKE_COMMAND ${CMAKE_COMMAND} - CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DONLY_CORE_LIBRARY=ON -DENABLE_SSL=OFF ${TOOLCHAIN} - BUILD_COMMAND $(MAKE) + CMAKE_ARGS -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DUSE_DYNAMIC_GOOGLE_LOG=OFF -DUSE_DYNAMIC_SQLITE=OFF -DENABLE_SSL=OFF ${TOOLCHAIN} + BUILD_COMMAND $(MAKE) CoreLibrary INSTALL_COMMAND "" BUILD_IN_SOURCE 0 )
--- a/Resources/Samples/RestApi/Sample.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/Samples/RestApi/Sample.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Resources/Samples/RestApiLinuxDynamic/Sample.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/Resources/Samples/RestApiLinuxDynamic/Sample.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Toolbox.lua Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,21 @@ +--[[ PrintRecursive(struct, [limit], [indent]) Recursively print arbitrary data. +Set limit (default 100) to stanch infinite loops. +Indents tables as [KEY] VALUE, nested tables as [KEY] [KEY]...[KEY] VALUE +Set indent ("") to prefix each line: Mytable [KEY] [KEY]...[KEY] VALUE +Source: https://gist.github.com/stuby/5445834#file-rprint-lua +--]] + +function PrintRecursive(s, l, i) -- recursive Print (structure, limit, indent) + l = (l) or 100; i = i or ""; -- default item limit, indent string + if (l<1) then print "ERROR: Item limit reached."; return l-1 end; + local ts = type(s); + if (ts ~= "table") then print (i,ts,s); return l-1 end + print (i,ts); -- print "table" + for k,v in pairs(s) do -- print "[KEY] VALUE" + l = PrintRecursive(v, l, i.."\t["..tostring(k).."]"); + if (l < 0) then break end + end + return l +end + +print('Lua toolbox installed')
--- a/THANKS Mon Apr 29 12:48:10 2013 +0200 +++ b/THANKS Wed Sep 18 15:27:07 2013 +0200 @@ -13,6 +13,11 @@ * Cyril Paulus (cyril.paulus@student.ulg.ac.be), for the build process and suggestions about the REST API. +* Will Ryder (will.ryder@sydney.edu.au), for improvements with the + handling of series with temporal positions (fMRI and dynamic PET). +* Ryan Walklin (ryanwalklin@gmail.com), for Mac OS X build. +* Peter Somlo (peter.somlo@gmail.com), for ClearCanvas support. +* 12maksqwe@gmail.com, for fixing issue #8. Artwork @@ -30,4 +35,10 @@ ------ * Mathieu Malaterre (mathieu.malaterre@gmail.com), for sponsoring Orthanc. -* Andreas Tille (andreas@an3as.eu), for help about Debian packaging. +* Andreas Tille (andreas@an3as.eu), for help about packaging. + + +Fedora and Red Hat +------------------ + +* Mario Ceresa (mrceresa@gmail.com), for help about packaging.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTests/Lua.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,72 @@ +#include "gtest/gtest.h" + +#include "../Core/Lua/LuaFunctionCall.h" + + +TEST(Lua, Simple) +{ + Orthanc::LuaContext lua; + lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + lua.Execute("a={}"); + lua.Execute("a['x'] = 10"); + lua.Execute("a['y'] = {}"); + lua.Execute("a['y'][1] = 20"); + lua.Execute("a['y'][2] = 20"); + lua.Execute("PrintRecursive(a)"); + + lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end"); + + Json::Value v, vv, o; + //v["a"] = "b"; + v.append("hello"); + v.append("world"); + v.append("42"); + vv.append("sub"); + vv.append("set"); + v.append(vv); + o = Json::objectValue; + o["x"] = 10; + o["y"] = 20; + o["z"] = 20.5f; + v.append(o); + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushJSON(v); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); + } + + o["bool"] = false; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_FALSE(f.ExecutePredicate()); + } + + o["bool"] = true; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_TRUE(f.ExecutePredicate()); + } +} + + +TEST(Lua, Existing) +{ + Orthanc::LuaContext lua; + lua.Execute("a={}"); + lua.Execute("function f() end"); + + ASSERT_TRUE(lua.IsExistingFunction("f")); + ASSERT_FALSE(lua.IsExistingFunction("a")); + ASSERT_FALSE(lua.IsExistingFunction("Dummy")); +}
--- a/UnitTests/MemoryCache.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/UnitTests/MemoryCache.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -8,46 +8,53 @@ #include "../Core/Cache/MemoryCache.h" -TEST(CacheIndex, Basic) +TEST(LRU, Basic) { - Orthanc::CacheIndex<std::string> r; + Orthanc::LeastRecentlyUsedIndex<std::string> r; r.Add("d"); r.Add("a"); r.Add("c"); r.Add("b"); - r.TagAsMostRecent("a"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("b"); - r.TagAsMostRecent("c"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("c"); + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); + ASSERT_EQ("a", r.GetOldest()); ASSERT_EQ("a", r.RemoveOldest()); + ASSERT_EQ("b", r.GetOldest()); ASSERT_EQ("b", r.RemoveOldest()); + ASSERT_EQ("d", r.GetOldest()); ASSERT_EQ("d", r.RemoveOldest()); + ASSERT_EQ("c", r.GetOldest()); ASSERT_EQ("c", r.RemoveOldest()); ASSERT_TRUE(r.IsEmpty()); + + ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException); + ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException); } -TEST(CacheIndex, Payload) +TEST(LRU, Payload) { - Orthanc::CacheIndex<std::string, int> r; + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; r.Add("a", 420); r.Add("b", 421); r.Add("c", 422); r.Add("d", 423); - r.TagAsMostRecent("a"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("b"); - r.TagAsMostRecent("c"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("c"); + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); ASSERT_TRUE(r.Contains("b")); ASSERT_EQ(421, r.Invalidate("b")); @@ -58,14 +65,76 @@ ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p); ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p); + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(420, r.GetOldestPayload()); ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(423, r.GetOldestPayload()); ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); + + ASSERT_EQ("c", r.GetOldest()); + ASSERT_EQ(422, r.GetOldestPayload()); ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); ASSERT_TRUE(r.IsEmpty()); } +TEST(LRU, PayloadUpdate) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("d", 423); + + r.MakeMostRecent("a", 424); + r.MakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + + +TEST(LRU, PayloadUpdateBis) +{ + Orthanc::LeastRecentlyUsedIndex<std::string, int> r; + + r.AddOrMakeMostRecent("a", 420); + r.AddOrMakeMostRecent("b", 421); + r.AddOrMakeMostRecent("d", 423); + r.AddOrMakeMostRecent("a", 424); + r.AddOrMakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + namespace
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTests/Png.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -0,0 +1,109 @@ +#include "gtest/gtest.h" + +#include <stdint.h> +#include "../Core/FileFormats/PngReader.h" +#include "../Core/FileFormats/PngWriter.h" + +TEST(PngWriter, ColorPattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 61; + int pitch = width * 3; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p += 3) + { + p[0] = (y % 3 == 0) ? 255 : 0; + p[1] = (y % 3 == 1) ? 255 : 0; + p[2] = (y % 3 == 2) ? 255 : 0; + } + } + + w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); +} + +TEST(PngWriter, Gray8Pattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 256; + int pitch = width; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p++) + { + *p = y; + } + } + + w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); +} + +TEST(PngWriter, Gray16Pattern) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); +} + +TEST(PngWriter, EndToEnd) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + std::string s; + w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + Orthanc::PngReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); + + v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r.GetBuffer() + y * r.GetPitch()); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + +}
--- a/UnitTests/PngWriter.cpp Mon Apr 29 12:48:10 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -#include "gtest/gtest.h" - -#include <stdint.h> -#include "../Core/PngWriter.h" - -TEST(PngWriter, ColorPattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 61; - int pitch = width * 3; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p += 3) - { - p[0] = (y % 3 == 0) ? 255 : 0; - p[1] = (y % 3 == 1) ? 255 : 0; - p[2] = (y % 3 == 2) ? 255 : 0; - } - } - - w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); -} - -TEST(PngWriter, Gray8Pattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 256; - int pitch = width; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p++) - { - *p = y; - } - } - - w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); -} - -TEST(PngWriter, Gray16Pattern) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector<uint8_t> image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); -}
--- a/UnitTests/ServerIndex.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/UnitTests/ServerIndex.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -5,6 +5,7 @@ #include <ctype.h> #include <glog/logging.h> +#include <algorithm> using namespace Orthanc; @@ -135,10 +136,25 @@ ASSERT_EQ("e", l.front()); } + std::list<MetadataType> md; + index.ListAvailableMetadata(md, a[4]); + ASSERT_EQ(0u, md.size()); + index.AddAttachment(a[4], FileInfo("my json file", FileContentType_Json, 42, CompressionType_Zlib, 21)); index.AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42)); index.AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44)); index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE"); + + index.ListAvailableMetadata(md, a[4]); + ASSERT_EQ(1u, md.size()); + ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); + index.SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU"); + index.ListAvailableMetadata(md, a[4]); + ASSERT_EQ(2u, md.size()); + index.DeleteMetadata(a[4], MetadataType_ModifiedFrom); + index.ListAvailableMetadata(md, a[4]); + ASSERT_EQ(1u, md.size()); + ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize()); ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize()); @@ -417,3 +433,57 @@ ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); } + + + +TEST(DatabaseWrapper, LookupTagValue) +{ + ServerIndexListener listener; + DatabaseWrapper index(listener); + + int64_t a[] = { + index.CreateResource("a", ResourceType_Study), // 0 + index.CreateResource("b", ResourceType_Study), // 1 + index.CreateResource("c", ResourceType_Study), // 2 + index.CreateResource("d", ResourceType_Series) // 3 + }; + + DicomMap m; + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[0], m); + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index.SetMainDicomTags(a[1], m); + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[2], m); + m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index.SetMainDicomTags(a[3], m); + + std::list<int64_t> s; + + index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0"); + ASSERT_EQ(2u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); + + index.LookupTagValue(s, "0"); + ASSERT_EQ(3u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end()); + + index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1"); + ASSERT_EQ(1u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); + + index.LookupTagValue(s, "1"); + ASSERT_EQ(1u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); + + + /*{ + std::list<std::string> s; + context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059"); + for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++) + { + std::cout << "*** " << *i << std::endl;; + } + }*/ + + +}
--- a/UnitTests/Versions.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/UnitTests/Versions.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -8,6 +8,7 @@ #include <curl/curl.h> #include <boost/version.hpp> #include <sqlite3.h> +#include <lua.h> TEST(Versions, Zlib) @@ -40,6 +41,14 @@ } +TEST(Versions, Lua) +{ + // Ensure that the Lua version is above 5.1.0. This version has + // introduced some API changes. + ASSERT_GE(LUA_VERSION_NUM, 501); +} + + #if ORTHANC_STATIC == 1 TEST(Versions, ZlibStatic) { @@ -48,7 +57,7 @@ TEST(Versions, BoostStatic) { - ASSERT_STREQ("1_49", BOOST_LIB_VERSION); + ASSERT_STREQ("1_54", BOOST_LIB_VERSION); } TEST(Versions, CurlStatic) @@ -63,7 +72,7 @@ ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); } -TEST(Versions, CurlSsl) +TEST(Versions, CurlSslStatic) { curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); @@ -76,4 +85,10 @@ ASSERT_TRUE(curlSupportsSsl); #endif } + +TEST(Version, LuaStatic) +{ + ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); +} #endif +
--- a/UnitTests/main.cpp Mon Apr 29 12:48:10 2013 +0200 +++ b/UnitTests/main.cpp Wed Sep 18 15:27:07 2013 +0200 @@ -1,16 +1,18 @@ +#include "../Core/EnumerationDictionary.h" + #include "gtest/gtest.h" #include <ctype.h> #include "../Core/Compression/ZlibCompressor.h" #include "../Core/DicomFormat/DicomTag.h" -#include "../OrthancCppClient/HttpClient.h" #include "../Core/HttpServer/HttpHandler.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" #include "../Core/Uuid.h" #include "../OrthancServer/FromDcmtkBridge.h" #include "../OrthancServer/OrthancInitialization.h" +#include "../Core/MultiThreading/SharedMessageQueue.h" using namespace Orthanc; @@ -29,6 +31,23 @@ ASSERT_FALSE(Toolbox::IsUuid("")); ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345")); ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000")); + ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000")); + ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000")); + ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok")); + ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok")); +} + +TEST(Toolbox, IsSHA1) +{ + ASSERT_FALSE(Toolbox::IsSHA1("")); + ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123")); + ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234")); + ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9")); + + std::string s; + Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); + ASSERT_TRUE(Toolbox::IsSHA1(s)); + ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); } TEST(Zlib, Basic) @@ -318,6 +337,149 @@ } +#if defined(__linux) +TEST(OrthancInitialization, AbsoluteDirectory) +{ + ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello")); + ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp")); +} +#endif + + + +#include "../OrthancServer/ServerEnumerations.h" + +TEST(EnumerationDictionary, Simple) +{ + Toolbox::EnumerationDictionary<MetadataType> d; + + ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException); + ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5")); + ASSERT_EQ(256, d.Translate("256")); + + d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate"); + + ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate")); + ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2")); + ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate)); + + ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException); + ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used + ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers + d.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); // ok +} + + +TEST(EnumerationDictionary, ServerEnumerations) +{ + ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient)); + ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study)); + ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series)); + ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance)); + + ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries)); + + ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure)); + ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success)); + + ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries)); + + ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries)); + ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate)); + + ASSERT_EQ(2047, StringToMetadata("2047")); + ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException); + ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024) + ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535) + RegisterUserMetadata(2047, "Ceci est un test"); + ASSERT_EQ(2047, StringToMetadata("2047")); + ASSERT_EQ(2047, StringToMetadata("Ceci est un test")); +} + + + +class DynamicInteger : public IDynamicObject +{ +private: + int value_; + +public: + DynamicInteger(int value) : value_(value) + { + } + + int GetValue() const + { + return value_; + } +}; + + +TEST(SharedMessageQueue, Basic) +{ + SharedMessageQueue q; + ASSERT_TRUE(q.WaitEmpty(0)); + q.Enqueue(new DynamicInteger(10)); + ASSERT_FALSE(q.WaitEmpty(1)); + q.Enqueue(new DynamicInteger(20)); + q.Enqueue(new DynamicInteger(30)); + q.Enqueue(new DynamicInteger(40)); + + std::auto_ptr<DynamicInteger> i; + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); + ASSERT_FALSE(q.WaitEmpty(1)); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); + ASSERT_TRUE(q.WaitEmpty(0)); + ASSERT_EQ(NULL, q.Dequeue(1)); +} + + +TEST(SharedMessageQueue, Clean) +{ + try + { + SharedMessageQueue q; + q.Enqueue(new DynamicInteger(10)); + q.Enqueue(new DynamicInteger(20)); + throw OrthancException("Nope"); + } + catch (OrthancException&) + { + } +} + + +TEST(Toolbox, WriteFile) +{ + std::string path; + + { + Toolbox::TemporaryFile tmp; + path = tmp.GetPath(); + + std::string s; + s.append("Hello"); + s.push_back('\0'); + s.append("World"); + ASSERT_EQ(11u, s.size()); + + Toolbox::WriteFile(s, path.c_str()); + + std::string t; + Toolbox::ReadFile(t, path.c_str()); + + ASSERT_EQ(11u, t.size()); + ASSERT_EQ(0, t[5]); + ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size())); + } + + std::string u; + ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException); +} + + int main(int argc, char **argv) { // Initialize Google's logging library. @@ -327,6 +489,8 @@ // Go to trace-level verbosity //FLAGS_v = 1; + Toolbox::DetectEndianness(); + google::InitGoogleLogging("Orthanc"); OrthancInitialize();