Mercurial > hg > orthanc
changeset 3786:3801435e34a1 SylvainRouquette/fix-issue169-95b752c
integration Orthanc-1.6.0->SylvainRouquette
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 19 Mar 2020 11:48:30 +0100 |
parents | 763533d6dd67 (current diff) 64a095d133a8 (diff) |
children | 7c853804f3b9 |
files | Core/DicomNetworking/DicomUserConnection.cpp Core/DicomNetworking/DicomUserConnection.h Core/DicomNetworking/RemoteModalityParameters.cpp Core/DicomNetworking/RemoteModalityParameters.h Resources/Patches/dcmtk-3.6.2-cmath.patch Resources/WebAssembly/dcdict.cc Resources/WebAssembly/dcdict.h UnitTestsSources/MultiThreadingTests.cpp |
diffstat | 547 files changed, 13485 insertions(+), 4013 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgtags Wed Mar 18 08:59:06 2020 +0100 +++ b/.hgtags Thu Mar 19 11:48:30 2020 +0100 @@ -1,1 +1,4 @@ a95beca72e99f3a1110cffd252bcf3abf5a2db27 dcmtk-3.6.1 +19966d29968506773f90b733b6e34559839ca5c7 toa2020012701 +dfd9a2229c18abd5c794d8fec967ef0ed10b8e91 toa2020012702 +799a8278b151222ea9e8b8628b1d57b5b7943f41 toa2020012703
--- a/CMakeLists.txt Wed Mar 18 08:59:06 2020 +0100 +++ b/CMakeLists.txt Thu Mar 19 11:48:30 2020 +0100 @@ -17,7 +17,7 @@ set(ENABLE_JPEG ON) set(ENABLE_LOCALE ON) set(ENABLE_LUA ON) -set(ENABLE_OPENSSL_ENGINES ON) +set(ENABLE_OPENSSL_ENGINES ON) # OpenSSL engines are necessary for PKCS11 set(ENABLE_PNG ON) set(ENABLE_PUGIXML ON) set(ENABLE_SQLITE ON) @@ -25,6 +25,9 @@ set(ENABLE_WEB_SERVER ON) set(ENABLE_ZLIB ON) +# To test transcoding +#set(ENABLE_DCMTK_TRANSCODING ON) + set(HAS_EMBEDDED_RESOURCES ON) @@ -37,6 +40,7 @@ SET(BUILD_MODALITY_WORKLISTS ON CACHE BOOL "Whether to build the sample plugin to serve modality worklists") SET(BUILD_RECOVER_COMPRESSED_FILE ON CACHE BOOL "Whether to build the companion tool to recover files compressed using Orthanc") SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Whether to build the ServeFolders plugin") +SET(BUILD_CONNECTIVITY_CHECKS ON CACHE BOOL "Whether to build the ConnectivityChecks plugin") SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") @@ -102,8 +106,10 @@ OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp OrthancServer/ServerJobs/ResourceModificationJob.cpp OrthancServer/ServerJobs/SplitStudyJob.cpp + OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp OrthancServer/ServerToolbox.cpp OrthancServer/SliceOrdering.cpp + OrthancServer/StorageCommitmentReports.cpp ) @@ -461,6 +467,67 @@ ##################################################################### +## Build the "ConnectivityChecks" plugin +##################################################################### + +if (ENABLE_PLUGINS AND BUILD_CONNECTIVITY_CHECKS) + include(ExternalProject) + + set(Flags) + + if (CMAKE_TOOLCHAIN_FILE) + # Take absolute path to the toolchain + get_filename_component(TMP ${CMAKE_TOOLCHAIN_FILE} REALPATH BASE ${CMAKE_SOURCE_DIR}) + list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${TMP}) + endif() + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + list(APPEND Flags + -DLSB_CC=${CMAKE_LSB_CC} + -DLSB_CXX=${CMAKE_LSB_CXX} + ) + endif() + + externalproject_add(ConnectivityChecks + SOURCE_DIR "${ORTHANC_ROOT}/Plugins/Samples/ConnectivityChecks" + + # We explicitly provide a build directory, in order to avoid paths + # that are too long on our Visual Studio 2008 CIS + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/ConnectivityChecks-build" + + CMAKE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} + -DPLUGIN_VERSION=${ORTHANC_VERSION} + -DSTATIC_BUILD=${STATIC_BUILD} + -DALLOW_DOWNLOADS=${ALLOW_DOWNLOADS} + -DUSE_LEGACY_JSONCPP=${USE_LEGACY_JSONCPP} + ${Flags} + ) + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + if (MSVC) + set(Prefix "") + else() + set(Prefix "lib") # MinGW + endif() + + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${Prefix}ConnectivityChecks.dll + DESTINATION "lib") + else() + list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix) + list(GET CMAKE_FIND_LIBRARY_SUFFIXES 0 Suffix) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${Prefix}ConnectivityChecks${Suffix} + ${CMAKE_CURRENT_BINARY_DIR}/${Prefix}ConnectivityChecks${Suffix}.${ORTHANC_VERSION} + DESTINATION "share/orthanc/plugins") + endif() +endif() + + + +##################################################################### ## Build the companion tool to recover files compressed using Orthanc #####################################################################
--- a/Core/Cache/ICachePageProvider.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Cache/ICachePageProvider.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -38,13 +38,16 @@ namespace Orthanc { - class ICachePageProvider + namespace Deprecated { - public: - virtual ~ICachePageProvider() + class ICachePageProvider { - } + public: + virtual ~ICachePageProvider() + { + } - virtual IDynamicObject* Provide(const std::string& id) = 0; - }; + virtual IDynamicObject* Provide(const std::string& id) = 0; + }; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/ICacheable.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ICacheable : public boost::noncopyable + { + public: + virtual ~ICacheable() + { + } + + virtual size_t GetMemoryUsage() const = 0; + }; +}
--- a/Core/Cache/LeastRecentlyUsedIndex.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Cache/LeastRecentlyUsedIndex.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Cache/MemoryCache.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Cache/MemoryCache.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -38,71 +38,74 @@ namespace Orthanc { - MemoryCache::Page& MemoryCache::Load(const std::string& id) + namespace Deprecated { - // Reuse the cache entry if it already exists - Page* p = NULL; - if (index_.Contains(id, p)) + MemoryCache::Page& MemoryCache::Load(const std::string& id) { - VLOG(1) << "Reusing a cache page"; - assert(p != NULL); - index_.MakeMostRecent(id); + // Reuse the cache entry if it already exists + Page* p = NULL; + if (index_.Contains(id, p)) + { + VLOG(1) << "Reusing a cache page"; + assert(p != NULL); + index_.MakeMostRecent(id); + return *p; + } + + // The id is not in the cache yet. Make some room if the cache + // is full. + if (index_.GetSize() == cacheSize_) + { + VLOG(1) << "Dropping the oldest cache page"; + index_.RemoveOldest(p); + delete p; + } + + // Create a new cache page + std::unique_ptr<Page> result(new Page); + result->id_ = id; + result->content_.reset(provider_.Provide(id)); + + // Add the newly create page to the cache + VLOG(1) << "Registering new data in a cache page"; + p = result.release(); + index_.Add(id, p); return *p; } - // The id is not in the cache yet. Make some room if the cache - // is full. - if (index_.GetSize() == cacheSize_) + MemoryCache::MemoryCache(ICachePageProvider& provider, + size_t cacheSize) : + provider_(provider), + cacheSize_(cacheSize) { - VLOG(1) << "Dropping the oldest cache page"; - index_.RemoveOldest(p); - delete p; } - // Create a new cache page - std::auto_ptr<Page> result(new Page); - result->id_ = id; - result->content_.reset(provider_.Provide(id)); - - // Add the newly create page to the cache - VLOG(1) << "Registering new data in a cache page"; - p = result.release(); - index_.Add(id, p); - return *p; - } + void MemoryCache::Invalidate(const std::string& id) + { + Page* p = NULL; + if (index_.Contains(id, p)) + { + VLOG(1) << "Invalidating a cache page"; + assert(p != NULL); + delete p; + index_.Invalidate(id); + } + } - MemoryCache::MemoryCache(ICachePageProvider& provider, - size_t cacheSize) : - provider_(provider), - cacheSize_(cacheSize) - { - } + MemoryCache::~MemoryCache() + { + while (!index_.IsEmpty()) + { + Page* element = NULL; + index_.RemoveOldest(element); + assert(element != NULL); + delete element; + } + } - void MemoryCache::Invalidate(const std::string& id) - { - Page* p = NULL; - if (index_.Contains(id, p)) + IDynamicObject& MemoryCache::Access(const std::string& id) { - VLOG(1) << "Invalidating a cache page"; - assert(p != NULL); - delete p; - index_.Invalidate(id); + return *Load(id).content_; } } - - MemoryCache::~MemoryCache() - { - while (!index_.IsEmpty()) - { - Page* element = NULL; - index_.RemoveOldest(element); - assert(element != NULL); - delete element; - } - } - - IDynamicObject& MemoryCache::Access(const std::string& id) - { - return *Load(id).content_; - } }
--- a/Core/Cache/MemoryCache.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Cache/MemoryCache.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,38 +33,43 @@ #pragma once -#include <memory> +#include "../Compatibility.h" +#include "ICachePageProvider.h" #include "LeastRecentlyUsedIndex.h" -#include "ICachePageProvider.h" + +#include <memory> namespace Orthanc { - /** - * WARNING: This class is NOT thread-safe. - **/ - class MemoryCache + namespace Deprecated { - private: - struct Page + /** + * WARNING: This class is NOT thread-safe. + **/ + class MemoryCache { - std::string id_; - std::auto_ptr<IDynamicObject> content_; - }; + private: + struct Page + { + std::string id_; + std::unique_ptr<IDynamicObject> content_; + }; - ICachePageProvider& provider_; - size_t cacheSize_; - LeastRecentlyUsedIndex<std::string, Page*> index_; + ICachePageProvider& provider_; + size_t cacheSize_; + LeastRecentlyUsedIndex<std::string, Page*> index_; - Page& Load(const std::string& id); + Page& Load(const std::string& id); - public: - MemoryCache(ICachePageProvider& provider, - size_t cacheSize); + public: + MemoryCache(ICachePageProvider& provider, + size_t cacheSize); - ~MemoryCache(); + ~MemoryCache(); - IDynamicObject& Access(const std::string& id); + IDynamicObject& Access(const std::string& id); - void Invalidate(const std::string& id); - }; + void Invalidate(const std::string& id); + }; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/MemoryObjectCache.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,282 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "../PrecompiledHeaders.h" +#include "MemoryObjectCache.h" + +#include "../Compatibility.h" + +namespace Orthanc +{ + class MemoryObjectCache::Item : public boost::noncopyable + { + private: + ICacheable* value_; + boost::posix_time::ptime time_; + + public: + explicit Item(ICacheable* value) : // Takes ownership + value_(value), + time_(boost::posix_time::second_clock::local_time()) + { + if (value == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + ~Item() + { + assert(value_ != NULL); + delete value_; + } + + ICacheable& GetValue() const + { + assert(value_ != NULL); + return *value_; + } + + const boost::posix_time::ptime& GetTime() const + { + return time_; + } + }; + + + void MemoryObjectCache::Recycle(size_t targetSize) + { + // WARNING: "cacheMutex_" must be locked + while (currentSize_ > targetSize) + { + assert(!content_.IsEmpty()); + + Item* item = NULL; + content_.RemoveOldest(item); + + assert(item != NULL); + const size_t size = item->GetValue().GetMemoryUsage(); + delete item; + + assert(currentSize_ >= size); + currentSize_ -= size; + } + + // Post-condition: "currentSize_ <= targetSize" + } + + + MemoryObjectCache::MemoryObjectCache() : + currentSize_(0), + maxSize_(100 * 1024 * 1024) // 100 MB + { + } + + + MemoryObjectCache::~MemoryObjectCache() + { + Recycle(0); + assert(content_.IsEmpty()); + } + + + size_t MemoryObjectCache::GetMaximumSize() + { +#if !defined(__EMSCRIPTEN__) + boost::mutex::scoped_lock lock(cacheMutex_); +#endif + + return maxSize_; + } + + + void MemoryObjectCache::SetMaximumSize(size_t size) + { + if (size == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + +#if !defined(__EMSCRIPTEN__) + // Make sure no accessor is currently open (as its data may be + // removed if recycling is needed) + WriterLock contentLock(contentMutex_); + + // Lock the global structure of the cache + boost::mutex::scoped_lock cacheLock(cacheMutex_); +#endif + + Recycle(size); + maxSize_ = size; + } + + + void MemoryObjectCache::Acquire(const std::string& key, + ICacheable* value) + { + std::unique_ptr<Item> item(new Item(value)); + + if (value == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { +#if !defined(__EMSCRIPTEN__) + // Make sure no accessor is currently open (as its data may be + // removed if recycling is needed) + WriterLock contentLock(contentMutex_); + + // Lock the global structure of the cache + boost::mutex::scoped_lock cacheLock(cacheMutex_); +#endif + + const size_t size = item->GetValue().GetMemoryUsage(); + + if (size > maxSize_) + { + // This object is too large to be stored in the cache, discard it + } + else if (content_.Contains(key)) + { + // Value already stored, don't overwrite the old value + content_.MakeMostRecent(key); + } + else + { + Recycle(maxSize_ - size); // Post-condition: currentSize_ <= maxSize_ - size + assert(currentSize_ + size <= maxSize_); + + content_.Add(key, item.release()); + currentSize_ += size; + } + } + } + + + void MemoryObjectCache::Invalidate(const std::string& key) + { +#if !defined(__EMSCRIPTEN__) + // Make sure no accessor is currently open (as it may correspond + // to the key to remove) + WriterLock contentLock(contentMutex_); + + // Lock the global structure of the cache + boost::mutex::scoped_lock cacheLock(cacheMutex_); +#endif + + Item* item = NULL; + if (content_.Contains(key, item)) + { + assert(item != NULL); + const size_t size = item->GetValue().GetMemoryUsage(); + delete item; + + content_.Invalidate(key); + + assert(currentSize_ >= size); + currentSize_ -= size; + } + } + + + MemoryObjectCache::Accessor::Accessor(MemoryObjectCache& cache, + const std::string& key, + bool unique) : + item_(NULL) + { +#if !defined(__EMSCRIPTEN__) + if (unique) + { + writerLock_ = WriterLock(cache.contentMutex_); + } + else + { + readerLock_ = ReaderLock(cache.contentMutex_); + } + + // Lock the global structure of the cache, must be *after* the + // reader/writer lock + cacheLock_ = boost::mutex::scoped_lock(cache.cacheMutex_); +#endif + + if (cache.content_.Contains(key, item_)) + { + cache.content_.MakeMostRecent(key); + } + +#if !defined(__EMSCRIPTEN__) + cacheLock_.unlock(); + + if (item_ == NULL) + { + // This item does not exist in the cache, we can release the + // reader/writer lock + if (unique) + { + writerLock_.unlock(); + } + else + { + readerLock_.unlock(); + } + } +#endif + } + + + ICacheable& MemoryObjectCache::Accessor::GetValue() const + { + if (IsValid()) + { + return item_->GetValue(); + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + const boost::posix_time::ptime& MemoryObjectCache::Accessor::GetTime() const + { + if (IsValid()) + { + return item_->GetTime(); + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/MemoryObjectCache.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "ICacheable.h" +#include "LeastRecentlyUsedIndex.h" + +#if !defined(__EMSCRIPTEN__) +// Multithreading is not supported in WebAssembly +# include <boost/thread/mutex.hpp> +# include <boost/thread/shared_mutex.hpp> +#endif + +#include <boost/date_time/posix_time/posix_time.hpp> + + +namespace Orthanc +{ + class MemoryObjectCache : public boost::noncopyable + { + private: + class Item; + +#if !defined(__EMSCRIPTEN__) + typedef boost::unique_lock<boost::shared_mutex> WriterLock; + typedef boost::shared_lock<boost::shared_mutex> ReaderLock; + + // This mutex protects modifications to the structure of the cache (monitor) + boost::mutex cacheMutex_; + + // This mutex protects modifications to the items that are stored in the cache + boost::shared_mutex contentMutex_; +#endif + + size_t currentSize_; + size_t maxSize_; + LeastRecentlyUsedIndex<std::string, Item*> content_; + + void Recycle(size_t targetSize); + + public: + MemoryObjectCache(); + + ~MemoryObjectCache(); + + size_t GetMaximumSize(); + + void SetMaximumSize(size_t size); + + void Acquire(const std::string& key, + ICacheable* value); + + void Invalidate(const std::string& key); + + class Accessor : public boost::noncopyable + { + private: +#if !defined(__EMSCRIPTEN__) + ReaderLock readerLock_; + WriterLock writerLock_; + boost::mutex::scoped_lock cacheLock_; +#endif + + Item* item_; + + public: + Accessor(MemoryObjectCache& cache, + const std::string& key, + bool unique); + + bool IsValid() const + { + return item_ != NULL; + } + + ICacheable& GetValue() const; + + const boost::posix_time::ptime& GetTime() const; + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/MemoryStringCache.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,84 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "../PrecompiledHeaders.h" +#include "MemoryStringCache.h" + +namespace Orthanc +{ + class MemoryStringCache::StringValue : public ICacheable + { + private: + std::string content_; + + public: + StringValue(const std::string& content) : + content_(content) + { + } + + const std::string& GetContent() const + { + return content_; + } + + virtual size_t GetMemoryUsage() const + { + return content_.size(); + } + }; + + + void MemoryStringCache::Add(const std::string& key, + const std::string& value) + { + cache_.Acquire(key, new StringValue(value)); + } + + + bool MemoryStringCache::Fetch(std::string& value, + const std::string& key) + { + MemoryObjectCache::Accessor reader(cache_, key, false /* multiple readers are allowed */); + + if (reader.IsValid()) + { + value = dynamic_cast<StringValue&>(reader.GetValue()).GetContent(); + return true; + } + else + { + return false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/MemoryStringCache.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "MemoryObjectCache.h" + +namespace Orthanc +{ + /** + * Facade object around "MemoryObjectCache" that caches a dictionary + * of strings, using the "fetch/add" paradigm of memcached. + **/ + class MemoryStringCache : public boost::noncopyable + { + private: + class StringValue; + + MemoryObjectCache cache_; + + public: + size_t GetMaximumSize() + { + return cache_.GetMaximumSize(); + } + + void SetMaximumSize(size_t size) + { + cache_.SetMaximumSize(size); + } + + void Add(const std::string& key, + const std::string& value); + + void Invalidate(const std::string& key) + { + cache_.Invalidate(key); + } + + bool Fetch(std::string& value, + const std::string& key); + }; +}
--- a/Core/Cache/SharedArchive.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Cache/SharedArchive.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Cache/SharedArchive.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Cache/SharedArchive.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -57,7 +57,7 @@ size_t maxSize_; boost::mutex mutex_; Archive archive_; - Orthanc::LeastRecentlyUsedIndex<std::string> lru_; + LeastRecentlyUsedIndex<std::string> lru_; void RemoveInternal(const std::string& id);
--- a/Core/ChunkedBuffer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/ChunkedBuffer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/ChunkedBuffer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/ChunkedBuffer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Compatibility.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,105 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 + +//#define Orthanc_Compatibility_h_STR2(x) #x +//#define Orthanc_Compatibility_h_STR1(x) Orthanc_Compatibility_h_STR2(x) + +//#pragma message("__cplusplus = " Orthanc_Compatibility_h_STR1(__cplusplus)) + +#if (defined _MSC_VER) +//# pragma message("_MSC_VER = " Orthanc_Compatibility_h_STR1(_MSC_VER)) +//# pragma message("_MSVC_LANG = " Orthanc_Compatibility_h_STR1(_MSVC_LANG)) +// The __cplusplus macro cannot be used in Visual C++ < 1914 (VC++ 15.7) +// However, even in recent versions, __cplusplus will only be correct (that is, +// correctly defines the supported C++ version) if a special flag is passed to +// the compiler ("/Zc:__cplusplus") +// To make this header more robust, we use the _MSVC_LANG equivalent macro. + +// please note that not all C++11 features are supported when _MSC_VER == 1600 +// (or higher). This header file can be made for fine-grained, if required, +// based on specific _MSC_VER values + +# if _MSC_VER >= 1600 +# define ORTHANC_Cxx03_DETECTED 0 +# else +# define ORTHANC_Cxx03_DETECTED 1 +# endif + +#else +// of _MSC_VER is not defined, we assume __cplusplus is correctly defined +// if __cplusplus is not defined (very old compilers??), then the following +// test will compare 0 < 201103L and will be true --> safe. +# if __cplusplus < 201103L +# define ORTHANC_Cxx03_DETECTED 1 +# else +# define ORTHANC_Cxx03_DETECTED 0 +# endif +#endif + +#if ORTHANC_Cxx03_DETECTED == 1 +//#pragma message("C++ 11 support is not present.") + +/** + * "std::unique_ptr" was introduced in C++11, and "std::auto_ptr" was + * removed in C++17. We emulate "std::auto_ptr" using boost: "The + * smart pointer unique_ptr [is] a drop-in replacement for + * std::unique_ptr, usable also from C++03 compilers." This is only + * available if Boost >= 1.57.0 (from November 2014). + * https://www.boost.org/doc/libs/1_57_0/doc/html/move/reference.html#header.boost.move.unique_ptr_hpp + **/ + +#include <boost/move/unique_ptr.hpp> + +namespace std +{ + template <typename T> + class unique_ptr : public boost::movelib::unique_ptr<T> + { + public: + explicit unique_ptr() : + boost::movelib::unique_ptr<T>() + { + } + + explicit unique_ptr(T* p) : + boost::movelib::unique_ptr<T>(p) + { + } + }; +} +#else +//# pragma message("C++ 11 support is present.") +# include <memory> +#endif
--- a/Core/Compression/DeflateBaseCompressor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/DeflateBaseCompressor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/DeflateBaseCompressor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/DeflateBaseCompressor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/GzipCompressor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/GzipCompressor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/GzipCompressor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/GzipCompressor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/HierarchicalZipWriter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/HierarchicalZipWriter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/HierarchicalZipWriter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/HierarchicalZipWriter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/IBufferCompressor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/IBufferCompressor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZipWriter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/ZipWriter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZipWriter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/ZipWriter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZlibCompressor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/ZlibCompressor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZlibCompressor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Compression/ZlibCompressor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomArray.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomArray.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -40,10 +40,10 @@ { DicomArray::DicomArray(const DicomMap& map) { - elements_.reserve(map.map_.size()); + elements_.reserve(map.content_.size()); - for (DicomMap::Map::const_iterator it = - map.map_.begin(); it != map.map_.end(); ++it) + for (DicomMap::Content::const_iterator it = + map.content_.begin(); it != map.content_.end(); ++it) { elements_.push_back(new DicomElement(it->first, *it->second)); }
--- a/Core/DicomFormat/DicomArray.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomArray.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomElement.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomElement.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomImageInformation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomImageInformation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -39,6 +39,7 @@ #include "DicomImageInformation.h" +#include "../Compatibility.h" #include "../OrthancException.h" #include "../Toolbox.h" #include <boost/lexical_cast.hpp> @@ -223,7 +224,7 @@ DicomImageInformation* DicomImageInformation::Clone() const { - std::auto_ptr<DicomImageInformation> target(new DicomImageInformation); + std::unique_ptr<DicomImageInformation> target(new DicomImageInformation); target->width_ = width_; target->height_ = height_; target->samplesPerPixel_ = samplesPerPixel_;
--- a/Core/DicomFormat/DicomImageInformation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomImageInformation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomInstanceHasher.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomInstanceHasher.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomInstanceHasher.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomMap.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomMap.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -37,6 +37,7 @@ #include <stdio.h> #include <memory> +#include "../Compatibility.h" #include "../Endianness.h" #include "../Logging.h" #include "../OrthancException.h" @@ -46,72 +47,87 @@ namespace Orthanc { - static DicomTag patientTags[] = + namespace { - //DicomTag(0x0010, 0x1010), // PatientAge - //DicomTag(0x0010, 0x1040) // PatientAddress - DicomTag(0x0010, 0x0010), // PatientName - DicomTag(0x0010, 0x0030), // PatientBirthDate - DicomTag(0x0010, 0x0040), // PatientSex - DicomTag(0x0010, 0x1000), // OtherPatientIDs - DICOM_TAG_PATIENT_ID - }; + struct MainDicomTag + { + const DicomTag tag_; + const char* name_; + }; + } - static DicomTag studyTags[] = + static const MainDicomTag PATIENT_MAIN_DICOM_TAGS[] = { - //DicomTag(0x0010, 0x1020), // PatientSize - //DicomTag(0x0010, 0x1030) // PatientWeight - DICOM_TAG_STUDY_DATE, - DicomTag(0x0008, 0x0030), // StudyTime - DicomTag(0x0020, 0x0010), // StudyID - DICOM_TAG_STUDY_DESCRIPTION, - DICOM_TAG_ACCESSION_NUMBER, - DICOM_TAG_STUDY_INSTANCE_UID, - DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, // New in db v6 - DICOM_TAG_INSTITUTION_NAME, // New in db v6 - DICOM_TAG_REQUESTING_PHYSICIAN, // New in db v6 - DICOM_TAG_REFERRING_PHYSICIAN_NAME // New in db v6 + // { DicomTag(0x0010, 0x1010), "PatientAge" }, + // { DicomTag(0x0010, 0x1040), "PatientAddress" }, + { DicomTag(0x0010, 0x0010), "PatientName" }, + { DicomTag(0x0010, 0x0030), "PatientBirthDate" }, + { DicomTag(0x0010, 0x0040), "PatientSex" }, + { DicomTag(0x0010, 0x1000), "OtherPatientIDs" }, + { DICOM_TAG_PATIENT_ID, "PatientID" } }; + + static const MainDicomTag STUDY_MAIN_DICOM_TAGS[] = + { + // { DicomTag(0x0010, 0x1020), "PatientSize" }, + // { DicomTag(0x0010, 0x1030), "PatientWeight" }, + { DICOM_TAG_STUDY_DATE, "StudyDate" }, + { DicomTag(0x0008, 0x0030), "StudyTime" }, + { DicomTag(0x0020, 0x0010), "StudyID" }, + { DICOM_TAG_STUDY_DESCRIPTION, "StudyDescription" }, + { DICOM_TAG_ACCESSION_NUMBER, "AccessionNumber" }, + { DICOM_TAG_STUDY_INSTANCE_UID, "StudyInstanceUID" }, - static DicomTag seriesTags[] = + // New in db v6 + { DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, "RequestedProcedureDescription" }, + { DICOM_TAG_INSTITUTION_NAME, "InstitutionName" }, + { DICOM_TAG_REQUESTING_PHYSICIAN, "RequestingPhysician" }, + { DICOM_TAG_REFERRING_PHYSICIAN_NAME, "ReferringPhysicianName" } + }; + + static const MainDicomTag SERIES_MAIN_DICOM_TAGS[] = { - //DicomTag(0x0010, 0x1080), // MilitaryRank - DicomTag(0x0008, 0x0021), // SeriesDate - DicomTag(0x0008, 0x0031), // SeriesTime - DICOM_TAG_MODALITY, - DicomTag(0x0008, 0x0070), // Manufacturer - DicomTag(0x0008, 0x1010), // StationName - DICOM_TAG_SERIES_DESCRIPTION, - DicomTag(0x0018, 0x0015), // BodyPartExamined - DicomTag(0x0018, 0x0024), // SequenceName - DicomTag(0x0018, 0x1030), // ProtocolName - DicomTag(0x0020, 0x0011), // SeriesNumber - 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, - DICOM_TAG_IMAGE_ORIENTATION_PATIENT, // New in db v6 - DICOM_TAG_SERIES_TYPE, // New in db v6 - DICOM_TAG_OPERATOR_NAME, // New in db v6 - DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, // New in db v6 - DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, // New in db v6 - DICOM_TAG_CONTRAST_BOLUS_AGENT // New in db v6 + // { DicomTag(0x0010, 0x1080), "MilitaryRank" }, + { DicomTag(0x0008, 0x0021), "SeriesDate" }, + { DicomTag(0x0008, 0x0031), "SeriesTime" }, + { DICOM_TAG_MODALITY, "Modality" }, + { DicomTag(0x0008, 0x0070), "Manufacturer" }, + { DicomTag(0x0008, 0x1010), "StationName" }, + { DICOM_TAG_SERIES_DESCRIPTION, "SeriesDescription" }, + { DicomTag(0x0018, 0x0015), "BodyPartExamined" }, + { DicomTag(0x0018, 0x0024), "SequenceName" }, + { DicomTag(0x0018, 0x1030), "ProtocolName" }, + { DicomTag(0x0020, 0x0011), "SeriesNumber" }, + { DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, "CardiacNumberOfImages" }, + { DICOM_TAG_IMAGES_IN_ACQUISITION, "ImagesInAcquisition" }, + { DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "NumberOfTemporalPositions" }, + { DICOM_TAG_NUMBER_OF_SLICES, "NumberOfSlices" }, + { DICOM_TAG_NUMBER_OF_TIME_SLICES, "NumberOfTimeSlices" }, + { DICOM_TAG_SERIES_INSTANCE_UID, "SeriesInstanceUID" }, + + // New in db v6 + { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" }, + { DICOM_TAG_SERIES_TYPE, "SeriesType" }, + { DICOM_TAG_OPERATOR_NAME, "OperatorsName" }, + { DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, "PerformedProcedureStepDescription" }, + { DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, "AcquisitionDeviceProcessingDescription" }, + { DICOM_TAG_CONTRAST_BOLUS_AGENT, "ContrastBolusAgent" } }; - - static DicomTag instanceTags[] = + + static const MainDicomTag INSTANCE_MAIN_DICOM_TAGS[] = { - DicomTag(0x0008, 0x0012), // InstanceCreationDate - DicomTag(0x0008, 0x0013), // InstanceCreationTime - DicomTag(0x0020, 0x0012), // AcquisitionNumber - DICOM_TAG_IMAGE_INDEX, - DICOM_TAG_INSTANCE_NUMBER, - DICOM_TAG_NUMBER_OF_FRAMES, - DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, - DICOM_TAG_SOP_INSTANCE_UID, - DICOM_TAG_IMAGE_POSITION_PATIENT, // New in db v6 - DICOM_TAG_IMAGE_COMMENTS, // New in db v6 + { DicomTag(0x0008, 0x0012), "InstanceCreationDate" }, + { DicomTag(0x0008, 0x0013), "InstanceCreationTime" }, + { DicomTag(0x0020, 0x0012), "AcquisitionNumber" }, + { DICOM_TAG_IMAGE_INDEX, "ImageIndex" }, + { DICOM_TAG_INSTANCE_NUMBER, "InstanceNumber" }, + { DICOM_TAG_NUMBER_OF_FRAMES, "NumberOfFrames" }, + { DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, "TemporalPositionIdentifier" }, + { DICOM_TAG_SOP_INSTANCE_UID, "SOPInstanceUID" }, + + // New in db v6 + { DICOM_TAG_IMAGE_POSITION_PATIENT, "ImagePositionPatient" }, + { DICOM_TAG_IMAGE_COMMENTS, "ImageComments" }, /** * Main DICOM tags that are not part of any release of the @@ -120,34 +136,34 @@ * access these tags if the corresponding DICOM files where * indexed in the database by an older version of Orthanc. **/ - DICOM_TAG_IMAGE_ORIENTATION_PATIENT // New in Orthanc 1.4.2 + { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" } // New in Orthanc 1.4.2 }; - void DicomMap::LoadMainDicomTags(const DicomTag*& tags, - size_t& size, - ResourceType level) + static void LoadMainDicomTags(const MainDicomTag*& tags, + size_t& size, + ResourceType level) { switch (level) { case ResourceType_Patient: - tags = patientTags; - size = sizeof(patientTags) / sizeof(DicomTag); + tags = PATIENT_MAIN_DICOM_TAGS; + size = sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); break; case ResourceType_Study: - tags = studyTags; - size = sizeof(studyTags) / sizeof(DicomTag); + tags = STUDY_MAIN_DICOM_TAGS; + size = sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); break; case ResourceType_Series: - tags = seriesTags; - size = sizeof(seriesTags) / sizeof(DicomTag); + tags = SERIES_MAIN_DICOM_TAGS; + size = sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); break; case ResourceType_Instance: - tags = instanceTags; - size = sizeof(instanceTags) / sizeof(DicomTag); + tags = INSTANCE_MAIN_DICOM_TAGS; + size = sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); break; default: @@ -156,55 +172,106 @@ } - void DicomMap::SetValue(uint16_t group, - uint16_t element, - DicomValue* value) + static void LoadMainDicomTags(std::map<DicomTag, std::string>& target, + ResourceType level) + { + const MainDicomTag* tags = NULL; + size_t size; + LoadMainDicomTags(tags, size, level); + + assert(tags != NULL && + size != 0); + + for (size_t i = 0; i < size; i++) + { + assert(target.find(tags[i].tag_) == target.end()); + + target[tags[i].tag_] = tags[i].name_; + } + } + + + namespace + { + class DicomTag2 : public DicomTag + { + public: + DicomTag2() : + DicomTag(0, 0) // To make std::map<> happy + { + } + + DicomTag2(const DicomTag& tag) : + DicomTag(tag) + { + } + }; + } + + + static void LoadMainDicomTags(std::map<std::string, DicomTag2>& target, + ResourceType level) + { + const MainDicomTag* tags = NULL; + size_t size; + LoadMainDicomTags(tags, size, level); + + assert(tags != NULL && + size != 0); + + for (size_t i = 0; i < size; i++) + { + assert(target.find(tags[i].name_) == target.end()); + + target[tags[i].name_] = tags[i].tag_; + } + } + + + void DicomMap::SetValueInternal(uint16_t group, + uint16_t element, + DicomValue* value) { DicomTag tag(group, element); - Map::iterator it = map_.find(tag); + Content::iterator it = content_.find(tag); - if (it != map_.end()) + if (it != content_.end()) { delete it->second; it->second = value; } else { - map_.insert(std::make_pair(tag, value)); + content_.insert(std::make_pair(tag, value)); } } - void DicomMap::SetValue(DicomTag tag, - DicomValue* value) - { - SetValue(tag.GetGroup(), tag.GetElement(), value); - } - void DicomMap::Clear() { - for (Map::iterator it = map_.begin(); it != map_.end(); ++it) + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) { assert(it->second != NULL); delete it->second; } - map_.clear(); + content_.clear(); } - void DicomMap::ExtractTags(DicomMap& result, - const DicomTag* tags, - size_t count) const + static void ExtractTags(DicomMap& result, + const DicomMap::Content& source, + const MainDicomTag* tags, + size_t count) { result.Clear(); for (unsigned int i = 0; i < count; i++) { - Map::const_iterator it = map_.find(tags[i]); - if (it != map_.end()) + DicomMap::Content::const_iterator it = source.find(tags[i].tag_); + if (it != source.end()) { - result.SetValue(it->first, it->second->Clone()); + result.SetValue(it->first, *it->second /* value will be cloned */); } } } @@ -212,33 +279,33 @@ void DicomMap::ExtractPatientInformation(DicomMap& result) const { - ExtractTags(result, patientTags, sizeof(patientTags) / sizeof(DicomTag)); + ExtractTags(result, content_, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); } void DicomMap::ExtractStudyInformation(DicomMap& result) const { - ExtractTags(result, studyTags, sizeof(studyTags) / sizeof(DicomTag)); + ExtractTags(result, content_, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); } void DicomMap::ExtractSeriesInformation(DicomMap& result) const { - ExtractTags(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag)); + ExtractTags(result, content_, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); } void DicomMap::ExtractInstanceInformation(DicomMap& result) const { - ExtractTags(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag)); + ExtractTags(result, content_, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); } DicomMap* DicomMap::Clone() const { - std::auto_ptr<DicomMap> result(new DicomMap); + std::unique_ptr<DicomMap> result(new DicomMap); - for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it) + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { - result->map_.insert(std::make_pair(it->first, it->second->Clone())); + result->content_.insert(std::make_pair(it->first, it->second->Clone())); } return result.release(); @@ -249,9 +316,9 @@ { Clear(); - for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it) + for (Content::const_iterator it = other.content_.begin(); it != other.content_.end(); ++it) { - map_.insert(std::make_pair(it->first, it->second->Clone())); + content_.insert(std::make_pair(it->first, it->second->Clone())); } } @@ -273,9 +340,9 @@ const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const { - Map::const_iterator it = map_.find(tag); + Content::const_iterator it = content_.find(tag); - if (it == map_.end()) + if (it == content_.end()) { return NULL; } @@ -288,35 +355,35 @@ void DicomMap::Remove(const DicomTag& tag) { - Map::iterator it = map_.find(tag); - if (it != map_.end()) + Content::iterator it = content_.find(tag); + if (it != content_.end()) { delete it->second; - map_.erase(it); + content_.erase(it); } } static void SetupFindTemplate(DicomMap& result, - const DicomTag* tags, + const MainDicomTag* tags, size_t count) { result.Clear(); for (size_t i = 0; i < count; i++) { - result.SetValue(tags[i], "", false); + result.SetValue(tags[i].tag_, "", false); } } void DicomMap::SetupFindPatientTemplate(DicomMap& result) { - SetupFindTemplate(result, patientTags, sizeof(patientTags) / sizeof(DicomTag)); + SetupFindTemplate(result, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); } void DicomMap::SetupFindStudyTemplate(DicomMap& result) { - SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag)); + SetupFindTemplate(result, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_PATIENT_ID, "", false); @@ -329,7 +396,7 @@ void DicomMap::SetupFindSeriesTemplate(DicomMap& result) { - SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag)); + SetupFindTemplate(result, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_PATIENT_ID, "", false); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); @@ -351,7 +418,7 @@ void DicomMap::SetupFindInstanceTemplate(DicomMap& result) { - SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag)); + SetupFindTemplate(result, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_PATIENT_ID, "", false); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); @@ -371,38 +438,13 @@ bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) { - DicomTag *tags = NULL; + const MainDicomTag *tags = NULL; size_t size; - - switch (level) - { - case ResourceType_Patient: - tags = patientTags; - size = sizeof(patientTags) / sizeof(DicomTag); - break; - - case ResourceType_Study: - tags = studyTags; - size = sizeof(studyTags) / sizeof(DicomTag); - break; - - case ResourceType_Series: - tags = seriesTags; - size = sizeof(seriesTags) / sizeof(DicomTag); - break; - - case ResourceType_Instance: - tags = instanceTags; - size = sizeof(instanceTags) / sizeof(DicomTag); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } + LoadMainDicomTags(tags, size, level); for (size_t i = 0; i < size; i++) { - if (tags[i] == tag) + if (tags[i].tag_ == tag) { return true; } @@ -422,38 +464,13 @@ void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level) { - DicomTag *tags = NULL; + const MainDicomTag *tags = NULL; size_t size; - - switch (level) - { - case ResourceType_Patient: - tags = patientTags; - size = sizeof(patientTags) / sizeof(DicomTag); - break; - - case ResourceType_Study: - tags = studyTags; - size = sizeof(studyTags) / sizeof(DicomTag); - break; - - case ResourceType_Series: - tags = seriesTags; - size = sizeof(seriesTags) / sizeof(DicomTag); - break; - - case ResourceType_Instance: - tags = instanceTags; - size = sizeof(instanceTags) / sizeof(DicomTag); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } + LoadMainDicomTags(tags, size, level); for (size_t i = 0; i < size; i++) { - result.insert(tags[i]); + result.insert(tags[i].tag_); } } @@ -479,8 +496,8 @@ { tags.clear(); - for (Map::const_iterator it = map_.begin(); - it != map_.end(); ++it) + for (Content::const_iterator it = content_.begin(); + it != content_.end(); ++it) { tags.insert(it->first); } @@ -728,9 +745,8 @@ } - bool DicomMap::ParseDicomMetaInformation(DicomMap& result, - const char* dicom, - size_t size) + bool DicomMap::IsDicomFile(const char* dicom, + size_t size) { /** * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html @@ -739,11 +755,19 @@ * account to determine whether the file is or is not a DICOM file. **/ - if (size < 132 || - dicom[128] != 'D' || - dicom[129] != 'I' || - dicom[130] != 'C' || - dicom[131] != 'M') + return (size >= 132 && + dicom[128] == 'D' && + dicom[129] == 'I' && + dicom[130] == 'C' && + dicom[131] == 'M'); + } + + + bool DicomMap::ParseDicomMetaInformation(DicomMap& result, + const char* dicom, + size_t size) + { + if (!IsDicomFile(dicom, size)) { return false; } @@ -968,6 +992,21 @@ } } + bool DicomMap::ParseFirstFloat(float& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseFirstFloat(result); + } + } + bool DicomMap::ParseDouble(double& result, const DicomTag& tag) const { @@ -1030,23 +1069,23 @@ void DicomMap::Merge(const DicomMap& other) { - for (Map::const_iterator it = other.map_.begin(); - it != other.map_.end(); ++it) + for (Content::const_iterator it = other.content_.begin(); + it != other.content_.end(); ++it) { assert(it->second != NULL); - if (map_.find(it->first) == map_.end()) + if (content_.find(it->first) == content_.end()) { - map_[it->first] = it->second->Clone(); + content_[it->first] = it->second->Clone(); } } } - void DicomMap::ExtractMainDicomTagsInternal(const DicomMap& other, - ResourceType level) + void DicomMap::MergeMainDicomTags(const DicomMap& other, + ResourceType level) { - const DicomTag* tags = NULL; + const MainDicomTag* tags = NULL; size_t size = 0; LoadMainDicomTags(tags, size, level); @@ -1054,13 +1093,13 @@ for (size_t i = 0; i < size; i++) { - Map::const_iterator found = other.map_.find(tags[i]); + Content::const_iterator found = other.content_.find(tags[i].tag_); - if (found != other.map_.end() && - map_.find(tags[i]) == map_.end()) + if (found != other.content_.end() && + content_.find(tags[i].tag_) == content_.end()) { assert(found->second != NULL); - map_[tags[i]] = found->second->Clone(); + content_[tags[i].tag_] = found->second->Clone(); } } } @@ -1069,10 +1108,10 @@ void DicomMap::ExtractMainDicomTags(const DicomMap& other) { Clear(); - ExtractMainDicomTagsInternal(other, ResourceType_Patient); - ExtractMainDicomTagsInternal(other, ResourceType_Study); - ExtractMainDicomTagsInternal(other, ResourceType_Series); - ExtractMainDicomTagsInternal(other, ResourceType_Instance); + MergeMainDicomTags(other, ResourceType_Patient); + MergeMainDicomTags(other, ResourceType_Study); + MergeMainDicomTags(other, ResourceType_Series); + MergeMainDicomTags(other, ResourceType_Instance); } @@ -1083,7 +1122,7 @@ std::set<DicomTag> mainDicomTags; GetMainDicomTags(mainDicomTags); - for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it) + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { if (mainDicomTags.find(it->first) == mainDicomTags.end()) { @@ -1099,7 +1138,7 @@ { target = Json::objectValue; - for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it) + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { assert(it->second != NULL); @@ -1129,15 +1168,15 @@ DicomTag tag(0, 0); if (!DicomTag::ParseHexadecimal(tag, tags[i].c_str()) || - map_.find(tag) != map_.end()) + content_.find(tag) != content_.end()) { throw OrthancException(ErrorCode_BadFileFormat); } - std::auto_ptr<DicomValue> value(new DicomValue); + std::unique_ptr<DicomValue> value(new DicomValue); value->Unserialize(source[tags[i]]); - map_[tag] = value.release(); + content_[tag] = value.release(); } } @@ -1294,6 +1333,87 @@ } + void DicomMap::RemoveBinaryTags() + { + Content kept; + + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(it->second != NULL); + + if (!it->second->IsBinary() && + !it->second->IsNull()) + { + kept[it->first] = it->second; + } + else + { + delete it->second; + } + } + + content_ = kept; + } + + + void DicomMap::DumpMainDicomTags(Json::Value& target, + ResourceType level) const + { + std::map<DicomTag, std::string> mainTags; // TODO - Create a singleton to hold this map + LoadMainDicomTags(mainTags, level); + + target = Json::objectValue; + + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(it->second != NULL); + + if (!it->second->IsBinary() && + !it->second->IsNull()) + { + std::map<DicomTag, std::string>::const_iterator found = mainTags.find(it->first); + + if (found != mainTags.end()) + { + target[found->second] = it->second->GetContent(); + } + } + } + } + + + void DicomMap::ParseMainDicomTags(const Json::Value& source, + ResourceType level) + { + if (source.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::map<std::string, DicomTag2> mainTags; // TODO - Create a singleton to hold this map + LoadMainDicomTags(mainTags, level); + + Json::Value::Members members = source.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + std::map<std::string, DicomTag2>::const_iterator found = mainTags.find(members[i]); + + if (found != mainTags.end()) + { + const Json::Value& value = source[members[i]]; + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + SetValue(found->second, value.asString(), false); + } + } + } + } + + void DicomMap::Print(FILE* fp) const { DicomArray a(*this);
--- a/Core/DicomFormat/DicomMap.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomMap.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -45,31 +45,23 @@ { class DicomMap : public boost::noncopyable { + public: + typedef std::map<DicomTag, DicomValue*> Content; + private: friend class DicomArray; friend class FromDcmtkBridge; friend class ParsedDicomFile; - typedef std::map<DicomTag, DicomValue*> Map; - - Map map_; + Content content_; // Warning: This takes the ownership of "value" - void SetValue(uint16_t group, - uint16_t element, - DicomValue* value); - - void SetValue(DicomTag tag, - DicomValue* value); + void SetValueInternal(uint16_t group, + uint16_t element, + DicomValue* value); - void ExtractTags(DicomMap& source, - const DicomTag* tags, - size_t count) const; - - static void GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level); - - void ExtractMainDicomTagsInternal(const DicomMap& other, - ResourceType level); + static void GetMainDicomTagsInternal(std::set<DicomTag>& result, + ResourceType level); public: DicomMap() @@ -83,7 +75,7 @@ size_t GetSize() const { - return map_.size(); + return content_.size(); } DicomMap* Clone() const; @@ -95,32 +87,32 @@ void SetNullValue(uint16_t group, uint16_t element) { - SetValue(group, element, new DicomValue); + SetValueInternal(group, element, new DicomValue); } void SetNullValue(const DicomTag& tag) { - SetValue(tag, new DicomValue); + SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue); } void SetValue(uint16_t group, uint16_t element, const DicomValue& value) { - SetValue(group, element, value.Clone()); + SetValueInternal(group, element, value.Clone()); } void SetValue(const DicomTag& tag, const DicomValue& value) { - SetValue(tag, value.Clone()); + SetValueInternal(tag.GetGroup(), tag.GetElement(), value.Clone()); } void SetValue(const DicomTag& tag, const std::string& str, bool isBinary) { - SetValue(tag, new DicomValue(str, isBinary)); + SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue(str, isBinary)); } void SetValue(uint16_t group, @@ -128,7 +120,7 @@ const std::string& str, bool isBinary) { - SetValue(group, element, new DicomValue(str, isBinary)); + SetValueInternal(group, element, new DicomValue(str, isBinary)); } bool HasTag(uint16_t group, uint16_t element) const @@ -138,7 +130,7 @@ bool HasTag(const DicomTag& tag) const { - return map_.find(tag) != map_.end(); + return content_.find(tag) != content_.end(); } const DicomValue& GetValue(uint16_t group, uint16_t element) const @@ -188,10 +180,9 @@ void GetTags(std::set<DicomTag>& tags) const; - static void LoadMainDicomTags(const DicomTag*& tags, - size_t& size, - ResourceType level); - + static bool IsDicomFile(const char* dicom, + size_t size); + static bool ParseDicomMetaInformation(DicomMap& result, const char* dicom, size_t size); @@ -217,6 +208,9 @@ bool ParseFloat(float& result, const DicomTag& tag) const; + bool ParseFirstFloat(float& result, + const DicomTag& tag) const; + bool ParseDouble(double& result, const DicomTag& tag) const; @@ -224,6 +218,9 @@ void Merge(const DicomMap& other); + void MergeMainDicomTags(const DicomMap& other, + ResourceType level); + void ExtractMainDicomTags(const DicomMap& other); bool HasOnlyMainDicomTags() const; @@ -238,6 +235,14 @@ const std::string& defaultValue, bool allowBinary) const; + void RemoveBinaryTags(); + + void DumpMainDicomTags(Json::Value& target, + ResourceType level) const; + + void ParseMainDicomTags(const Json::Value& source, + ResourceType level); + void Print(FILE* fp) const; // For debugging only }; }
--- a/Core/DicomFormat/DicomTag.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomTag.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -187,6 +187,10 @@ if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT) return "ImageOrientationPatient"; + // New in Orthanc 1.6.0, as tagged as "RETIRED_" since DCMTK 3.6.4 + if (*this == DICOM_TAG_OTHER_PATIENT_IDS) + return "OtherPatientIDs"; + return ""; }
--- a/Core/DicomFormat/DicomTag.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomTag.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -149,6 +149,8 @@ static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037); static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032); + static const DicomTag DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE(0x0028, 0x0107); + static const DicomTag DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE(0x0028, 0x0106); // Tags related to date and time static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022); @@ -189,6 +191,7 @@ static const DicomTag DICOM_TAG_PATIENT_COMMENTS(0x0010, 0x4000); static const DicomTag DICOM_TAG_PATIENT_SPECIES_DESCRIPTION(0x0010, 0x2201); static const DicomTag DICOM_TAG_STUDY_COMMENTS(0x0032, 0x4000); + static const DicomTag DICOM_TAG_OTHER_PATIENT_IDS(0x0010, 0x1000); // Tags used within the Stone of Orthanc static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009); @@ -206,6 +209,7 @@ static const DicomTag DICOM_TAG_RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1101); static const DicomTag DICOM_TAG_GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1102); static const DicomTag DICOM_TAG_BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1103); + static const DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050); // Counting patients, studies and series // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances @@ -229,4 +233,11 @@ static const DicomTag DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE(0x3006, 0x0010); static const DicomTag DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE(0x3006, 0x0012); static const DicomTag DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE(0x3006, 0x0014); + + // Tags for DICOMDIR + static const DicomTag DICOM_TAG_DIRECTORY_RECORD_TYPE(0x0004, 0x1430); + static const DicomTag DICOM_TAG_OFFSET_OF_THE_NEXT_DIRECTORY_RECORD(0x0004, 0x1400); + static const DicomTag DICOM_TAG_OFFSET_OF_REFERENCED_LOWER_LEVEL_DIRECTORY_ENTITY(0x0004, 0x1420); + static const DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID_IN_FILE(0x0004, 0x1511); + static const DicomTag DICOM_TAG_REFERENCED_FILE_ID(0x0004, 0x1500); }
--- a/Core/DicomFormat/DicomValue.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomValue.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -231,6 +231,11 @@ return ParseValue<double, true>(result, *this); } + bool DicomValue::ParseFirstFloat(float& result) const + { + return ParseFirstValue<float, true>(result, *this); + } + bool DicomValue::ParseFirstUnsignedInteger(unsigned int& result) const { return ParseFirstValue<unsigned int, true>(result, *this);
--- a/Core/DicomFormat/DicomValue.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomFormat/DicomValue.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -112,6 +112,8 @@ bool ParseDouble(double& result) const; + bool ParseFirstFloat(float& result) const; + bool ParseFirstUnsignedInteger(unsigned int& result) const; void Serialize(Json::Value& target) const;
--- a/Core/DicomNetworking/DicomFindAnswers.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/DicomFindAnswers.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -46,7 +46,7 @@ { void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer) { - std::auto_ptr<ParsedDicomFile> protection(answer); + std::unique_ptr<ParsedDicomFile> protection(answer); if (isWorklist_) { @@ -166,7 +166,7 @@ DcmDataset& source = *GetAnswer(index).GetDcmtkObject().getDataset(); - std::auto_ptr<DcmDataset> target(new DcmDataset); + std::unique_ptr<DcmDataset> target(new DcmDataset); for (unsigned long i = 0; i < source.card(); i++) {
--- a/Core/DicomNetworking/DicomFindAnswers.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/DicomFindAnswers.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/DicomServer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/DicomServer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -53,7 +53,7 @@ { boost::thread thread_; T_ASC_Network *network_; - std::auto_ptr<RunnableWorkersPool> workers_; + std::unique_ptr<RunnableWorkersPool> workers_; }; @@ -65,7 +65,7 @@ { /* receive an association and acknowledge or reject it. If the association was */ /* acknowledged, offer corresponding services and invoke one or more if required. */ - std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_)); + std::unique_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_)); try { @@ -94,6 +94,7 @@ moveRequestHandlerFactory_ = NULL; storeRequestHandlerFactory_ = NULL; worklistRequestHandlerFactory_ = NULL; + storageCommitmentFactory_ = NULL; applicationEntityFilter_ = NULL; checkCalledAet_ = true; associationTimeout_ = 30; @@ -289,6 +290,29 @@ } } + void DicomServer::SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& factory) + { + Stop(); + storageCommitmentFactory_ = &factory; + } + + bool DicomServer::HasStorageCommitmentRequestHandlerFactory() const + { + return (storageCommitmentFactory_ != NULL); + } + + IStorageCommitmentRequestHandlerFactory& DicomServer::GetStorageCommitmentRequestHandlerFactory() const + { + if (HasStorageCommitmentRequestHandlerFactory()) + { + return *storageCommitmentFactory_; + } + else + { + throw OrthancException(ErrorCode_NoStorageCommitmentHandler); + } + } + void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory) { Stop(); @@ -378,5 +402,4 @@ return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle()); } } - }
--- a/Core/DicomNetworking/DicomServer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/DicomServer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -41,6 +41,7 @@ #include "IMoveRequestHandlerFactory.h" #include "IStoreRequestHandlerFactory.h" #include "IWorklistRequestHandlerFactory.h" +#include "IStorageCommitmentRequestHandlerFactory.h" #include "IApplicationEntityFilter.h" #include "RemoteModalityParameters.h" @@ -82,6 +83,7 @@ IMoveRequestHandlerFactory* moveRequestHandlerFactory_; IStoreRequestHandlerFactory* storeRequestHandlerFactory_; IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_; + IStorageCommitmentRequestHandlerFactory* storageCommitmentFactory_; IApplicationEntityFilter* applicationEntityFilter_; static void ServerThread(DicomServer* server); @@ -122,6 +124,10 @@ bool HasWorklistRequestHandlerFactory() const; IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const; + void SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& handler); + bool HasStorageCommitmentRequestHandlerFactory() const; + IStorageCommitmentRequestHandlerFactory& GetStorageCommitmentRequestHandlerFactory() const; + void SetApplicationEntityFilter(IApplicationEntityFilter& handler); bool HasApplicationEntityFilter() const; IApplicationEntityFilter& GetApplicationEntityFilter() const;
--- a/Core/DicomNetworking/DicomUserConnection.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -86,6 +86,7 @@ # error The macro DCMTK_VERSION_NUMBER must be defined #endif +#include "../Compatibility.h" #include "../DicomFormat/DicomArray.h" #include "../Logging.h" #include "../OrthancException.h" @@ -158,7 +159,9 @@ void CheckIsOpen() const; - void Store(DcmInputStream& is, + void Store(std::string& sopClassUidOut /* out */, + std::string& sopInstanceUidOut /* out */, + DcmInputStream& is, DicomUserConnection& connection, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); @@ -252,7 +255,8 @@ } - void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) + void DicomUserConnection::SetupPresentationContexts(Mode mode, + const std::string& preferredTransferSyntax) { // Flatten an array with the preferred transfer syntax const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; @@ -274,30 +278,73 @@ } CheckStorageSOPClassesInvariant(); - unsigned int presentationContextId = 1; + + switch (mode) + { + case Mode_Generic: + { + unsigned int presentationContextId = 1; + + for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); + it != reservedStorageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback, remoteAet_); + } - for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); - it != reservedStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } + for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); + it != storageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback, remoteAet_); + } + + for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); + it != defaultStorageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback, remoteAet_); + } + + break; + } - for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); - it != storageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); - } + case Mode_RequestStorageCommitment: + case Mode_ReportStorageCommitment: + { + const char* as = UID_StorageCommitmentPushModelSOPClass; + + std::vector<const char*> ts; + ts.push_back(UID_LittleEndianExplicitTransferSyntax); + ts.push_back(UID_LittleEndianImplicitTransferSyntax); - for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); - it != defaultStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback, remoteAet_); + T_ASC_SC_ROLE role; + switch (mode) + { + case Mode_RequestStorageCommitment: + role = ASC_SC_ROLE_DEFAULT; + break; + + case Mode_ReportStorageCommitment: + role = ASC_SC_ROLE_SCP; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + Check(ASC_addPresentationContext(pimpl_->params_, 1 /*presentationContextId*/, + as, &ts[0], ts.size(), role), + remoteAet_, "initializing"); + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); } } - + static bool IsGenericTransferSyntax(const std::string& syntax) { @@ -307,7 +354,9 @@ } - void DicomUserConnection::PImpl::Store(DcmInputStream& is, + void DicomUserConnection::PImpl::Store(std::string& sopClassUidOut, + std::string& sopInstanceUidOut, + DcmInputStream& is, DicomUserConnection& connection, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) @@ -390,6 +439,9 @@ connection.remoteAet_); } + sopClassUidOut.assign(sopClass); + sopInstanceUidOut.assign(sopInstance); + // Figure out which of the accepted presentation contexts should be used int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); if (presID == 0) @@ -422,18 +474,37 @@ } // Finally conduct transmission of data - T_DIMSE_C_StoreRSP rsp; + T_DIMSE_C_StoreRSP response; DcmDataset* statusDetail = NULL; Check(DIMSE_storeUser(assoc_, presID, &request, NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL, - /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_, - &rsp, &statusDetail, NULL), + /*opt_blockMode*/ (dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), + /*opt_dimse_timeout*/ dimseTimeout_, + &response, &statusDetail, NULL), connection.remoteAet_, "C-STORE"); if (statusDetail != NULL) { delete statusDetail; } + + + /** + * New in Orthanc 1.6.0: Deal with failures during C-STORE. + * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1 + **/ + + if (response.DimseStatus != 0x0000 && // Success + response.DimseStatus != 0xB000 && // Warning - Coercion of Data Elements + response.DimseStatus != 0xB007 && // Warning - Data Set does not match SOP Class + response.DimseStatus != 0xB006) // Warning - Elements Discarded + { + char buf[16]; + sprintf(buf, "%04X", response.DimseStatus); + throw OrthancException(ErrorCode_NetworkProtocol, + "C-STORE SCU to AET \"" + connection.remoteAet_ + + "\" has failed with DIMSE status 0x" + buf); + } } @@ -565,7 +636,7 @@ case ModalityManufacturer_GenericNoWildcardInDates: case ModalityManufacturer_GenericNoUniversalWildcard: { - std::auto_ptr<DicomMap> fix(fields.Clone()); + std::unique_ptr<DicomMap> fix(fields.Clone()); std::set<DicomTag> tags; fix->GetTags(tags); @@ -642,7 +713,7 @@ responseCount, #endif FindCallback, &payload, - /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_blockMode*/ (dimseTimeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), /*opt_dimse_timeout*/ dimseTimeout, &response, &statusDetail); @@ -652,6 +723,24 @@ } Check(cond, remoteAet, "C-FIND"); + + + /** + * New in Orthanc 1.6.0: Deal with failures during C-FIND. + * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1 + **/ + + if (response.DimseStatus != 0x0000 && // Success + response.DimseStatus != 0xFF00 && // Pending - Matches are continuing + response.DimseStatus != 0xFF01) // Pending - Matches are continuing + { + char buf[16]; + sprintf(buf, "%04X", response.DimseStatus); + throw OrthancException(ErrorCode_NetworkProtocol, + "C-FIND SCU to AET \"" + remoteAet + + "\" has failed with DIMSE status 0x" + buf); + } + } @@ -662,7 +751,7 @@ { CheckIsOpen(); - std::auto_ptr<ParsedDicomFile> query; + std::unique_ptr<ParsedDicomFile> query; if (normalize) { @@ -703,22 +792,8 @@ break; case ResourceType_Instance: - clevel = "INSTANCE"; - if (manufacturer_ == ModalityManufacturer_ClearCanvas || - manufacturer_ == ModalityManufacturer_Dcm4Chee || - manufacturer_ == ModalityManufacturer_GE) - { - // 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 - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - clevel = "IMAGE"; - } - else - { - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "INSTANCE"); - } - + clevel = "IMAGE"; + DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; break; @@ -789,7 +864,7 @@ { CheckIsOpen(); - std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); + std::unique_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); DcmDataset* dataset = query->GetDcmtkObject().getDataset(); const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; @@ -808,19 +883,7 @@ break; case ResourceType_Instance: - if (manufacturer_ == ModalityManufacturer_ClearCanvas || - manufacturer_ == ModalityManufacturer_Dcm4Chee || - manufacturer_ == ModalityManufacturer_GE) - { - // 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 - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - } - else - { - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "INSTANCE"); - } + DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); break; default: @@ -848,7 +911,7 @@ DcmDataset* responseIdentifiers = NULL; OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset, NULL, NULL, - /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, pimpl_->net_, NULL, NULL, &response, &statusDetail, &responseIdentifiers); @@ -864,6 +927,22 @@ } Check(cond, remoteAet_, "C-MOVE"); + + + /** + * New in Orthanc 1.6.0: Deal with failures during C-MOVE. + * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2 + **/ + + if (response.DimseStatus != 0x0000 && // Success + response.DimseStatus != 0xFF00) // Pending - Sub-operations are continuing + { + char buf[16]; + sprintf(buf, "%04X", response.DimseStatus); + throw OrthancException(ErrorCode_NetworkProtocol, + "C-MOVE SCU to AET \"" + remoteAet_ + + "\" has failed with DIMSE status 0x" + buf); + } } @@ -1023,7 +1102,7 @@ } } - void DicomUserConnection::Open() + void DicomUserConnection::OpenInternal(Mode mode) { if (IsOpen()) { @@ -1063,7 +1142,7 @@ Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false), remoteAet_, "connecting"); - SetupPresentationContexts(preferredTransferSyntax_); + SetupPresentationContexts(mode, preferredTransferSyntax_); // Do the association Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_), @@ -1107,7 +1186,9 @@ return pimpl_->IsOpen(); } - void DicomUserConnection::Store(const char* buffer, + void DicomUserConnection::Store(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const char* buffer, size_t size, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) @@ -1118,26 +1199,31 @@ is.setBuffer(buffer, size); is.setEos(); - pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); + pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); } - void DicomUserConnection::Store(const std::string& buffer, + void DicomUserConnection::Store(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const std::string& buffer, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { if (buffer.size() > 0) - Store(&buffer[0], buffer.size(), moveOriginatorAET, moveOriginatorID); + Store(sopClassUid, sopInstanceUid, &buffer[0], buffer.size(), + moveOriginatorAET, moveOriginatorID); else - Store(NULL, 0, moveOriginatorAET, moveOriginatorID); + Store(sopClassUid, sopInstanceUid, NULL, 0, moveOriginatorAET, moveOriginatorID); } - void DicomUserConnection::StoreFile(const std::string& path, + void DicomUserConnection::StoreFile(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const std::string& path, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { // Prepare an input stream for the file DcmInputFileStream is(path.c_str()); - pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); + pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); } bool DicomUserConnection::Echo() @@ -1145,7 +1231,7 @@ CheckIsOpen(); DIC_US status; Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, - /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, &status, NULL), remoteAet_, "C-ECHO"); return status == STATUS_Success; @@ -1265,7 +1351,7 @@ { dcmConnectionTimeout.set(seconds); pimpl_->dimseTimeout_ = seconds; - pimpl_->acseTimeout_ = 10; // Timeout used during association negociation + pimpl_->acseTimeout_ = seconds; // Timeout used during association negociation and ASC_releaseAssociation() } } @@ -1278,7 +1364,7 @@ */ dcmConnectionTimeout.set(-1); pimpl_->dimseTimeout_ = 0; - pimpl_->acseTimeout_ = 10; // Timeout used during association negociation + pimpl_->acseTimeout_ = 10; // Timeout used during association negociation and ASC_releaseAssociation() } @@ -1368,4 +1454,369 @@ remotePort_ == remote.GetPortNumber() && manufacturer_ == remote.GetManufacturer()); } + + + static void FillSopSequence(DcmDataset& dataset, + const DcmTagKey& tag, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::vector<StorageCommitmentFailureReason>& failureReasons, + bool hasFailureReasons) + { + assert(sopClassUids.size() == sopInstanceUids.size() && + (hasFailureReasons ? + failureReasons.size() == sopClassUids.size() : + failureReasons.empty())); + + if (sopInstanceUids.empty()) + { + // Add an empty sequence + if (!dataset.insertEmptyElement(tag).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + else + { + for (size_t i = 0; i < sopClassUids.size(); i++) + { + std::unique_ptr<DcmItem> item(new DcmItem); + if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() || + !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() || + (hasFailureReasons && + !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) || + !dataset.insertSequenceItem(tag, item.release()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + } + } + + + + + void DicomUserConnection::ReportStorageCommitment( + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::vector<StorageCommitmentFailureReason>& failureReasons) + { + if (sopClassUids.size() != sopInstanceUids.size() || + sopClassUids.size() != failureReasons.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (IsOpen()) + { + Close(); + } + + std::vector<std::string> successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids; + std::vector<StorageCommitmentFailureReason> failedReasons; + + successSopClassUids.reserve(sopClassUids.size()); + successSopInstanceUids.reserve(sopClassUids.size()); + failedSopClassUids.reserve(sopClassUids.size()); + failedSopInstanceUids.reserve(sopClassUids.size()); + failedReasons.reserve(sopClassUids.size()); + + for (size_t i = 0; i < sopClassUids.size(); i++) + { + switch (failureReasons[i]) + { + case StorageCommitmentFailureReason_Success: + successSopClassUids.push_back(sopClassUids[i]); + successSopInstanceUids.push_back(sopInstanceUids[i]); + break; + + case StorageCommitmentFailureReason_ProcessingFailure: + case StorageCommitmentFailureReason_NoSuchObjectInstance: + case StorageCommitmentFailureReason_ResourceLimitation: + case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: + case StorageCommitmentFailureReason_ClassInstanceConflict: + case StorageCommitmentFailureReason_DuplicateTransactionUID: + failedSopClassUids.push_back(sopClassUids[i]); + failedSopInstanceUids.push_back(sopInstanceUids[i]); + failedReasons.push_back(failureReasons[i]); + break; + + default: + { + char buf[16]; + sprintf(buf, "%04xH", failureReasons[i]); + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Unsupported failure reason for storage commitment: " + std::string(buf)); + } + } + } + + try + { + OpenInternal(Mode_ReportStorageCommitment); + + /** + * N-EVENT-REPORT + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 + * + * Status code: + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 + **/ + + /** + * Send the "EVENT_REPORT_RQ" request + **/ + + LOG(INFO) << "Reporting modality \"" << remoteAet_ + << "\" about storage commitment transaction: " << transactionUid + << " (" << successSopClassUids.size() << " successes, " + << failedSopClassUids.size() << " failures)"; + const DIC_US messageId = pimpl_->assoc_->nextMsgID++; + + { + T_DIMSE_Message message; + memset(&message, 0, sizeof(message)); + message.CommandField = DIMSE_N_EVENT_REPORT_RQ; + + T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ; + content.MessageID = messageId; + strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); + strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); + content.DataSetType = DIMSE_DATASET_PRESENT; + + DcmDataset dataset; + if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + + { + std::vector<StorageCommitmentFailureReason> empty; + FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, + successSopInstanceUids, empty, false); + } + + // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html + if (failedSopClassUids.empty()) + { + content.EventTypeID = 1; // "Storage Commitment Request Successful" + } + else + { + content.EventTypeID = 2; // "Storage Commitment Request Complete - Failures Exist" + + // Failure reason + // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids, + failedSopInstanceUids, failedReasons, true); + } + + int presID = ASC_findAcceptedPresentationContextID( + pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); + if (presID == 0) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "Unable to send N-EVENT-REPORT request to AET: " + remoteAet_); + } + + if (!DIMSE_sendMessageUsingMemoryData( + pimpl_->assoc_, presID, &message, NULL /* status detail */, + &dataset, NULL /* callback */, NULL /* callback context */, + NULL /* commandSet */).good()) + { + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + /** + * Read the "EVENT_REPORT_RSP" response + **/ + + { + T_ASC_PresentationContextID presID = 0; + T_DIMSE_Message message; + + const int timeout = pimpl_->dimseTimeout_; + if (!DIMSE_receiveCommand(pimpl_->assoc_, + (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, + &presID, &message, NULL /* no statusDetail */).good() || + message.CommandField != DIMSE_N_EVENT_REPORT_RSP) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "Unable to read N-EVENT-REPORT response from AET: " + remoteAet_); + } + + const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP; + if (content.MessageIDBeingRespondedTo != messageId || + !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) || + !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) || + //(content.opts & O_NEVENTREPORT_EVENTTYPEID) || // Pedantic test - The "content.EventTypeID" is not used by Orthanc + std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || + std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || + content.DataSetType != DIMSE_DATASET_NULL) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "Badly formatted N-EVENT-REPORT response from AET: " + remoteAet_); + } + + if (content.DimseStatus != 0 /* success */) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "The request cannot be handled by remote AET: " + remoteAet_); + } + } + + Close(); + } + catch (OrthancException&) + { + Close(); + throw; + } + } + + + + void DicomUserConnection::RequestStorageCommitment( + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids) + { + if (sopClassUids.size() != sopInstanceUids.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < sopClassUids.size(); i++) + { + if (sopClassUids[i].empty() || + sopInstanceUids[i].empty()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "The SOP class/instance UIDs cannot be empty, found: \"" + + sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\""); + } + } + + if (transactionUid.size() < 5 || + transactionUid.substr(0, 5) != "2.25.") + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (IsOpen()) + { + Close(); + } + + try + { + OpenInternal(Mode_RequestStorageCommitment); + + /** + * N-ACTION + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 + * + * Status code: + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 + **/ + + /** + * Send the "N_ACTION_RQ" request + **/ + + LOG(INFO) << "Request to modality \"" << remoteAet_ + << "\" about storage commitment for " << sopClassUids.size() + << " instances, with transaction UID: " << transactionUid; + const DIC_US messageId = pimpl_->assoc_->nextMsgID++; + + { + T_DIMSE_Message message; + memset(&message, 0, sizeof(message)); + message.CommandField = DIMSE_N_ACTION_RQ; + + T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ; + content.MessageID = messageId; + strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); + strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); + content.ActionTypeID = 1; // "Request Storage Commitment" + content.DataSetType = DIMSE_DATASET_PRESENT; + + DcmDataset dataset; + if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + + { + std::vector<StorageCommitmentFailureReason> empty; + FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false); + } + + int presID = ASC_findAcceptedPresentationContextID( + pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); + if (presID == 0) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "Unable to send N-ACTION request to AET: " + remoteAet_); + } + + if (!DIMSE_sendMessageUsingMemoryData( + pimpl_->assoc_, presID, &message, NULL /* status detail */, + &dataset, NULL /* callback */, NULL /* callback context */, + NULL /* commandSet */).good()) + { + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + /** + * Read the "N_ACTION_RSP" response + **/ + + { + T_ASC_PresentationContextID presID = 0; + T_DIMSE_Message message; + + const int timeout = pimpl_->dimseTimeout_; + if (!DIMSE_receiveCommand(pimpl_->assoc_, + (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, + &presID, &message, NULL /* no statusDetail */).good() || + message.CommandField != DIMSE_N_ACTION_RSP) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "Unable to read N-ACTION response from AET: " + remoteAet_); + } + + const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP; + if (content.MessageIDBeingRespondedTo != messageId || + !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) || + !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) || + //(content.opts & O_NACTION_ACTIONTYPEID) || // Pedantic test - The "content.ActionTypeID" is not used by Orthanc + std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || + std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || + content.DataSetType != DIMSE_DATASET_NULL) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "Badly formatted N-ACTION response from AET: " + remoteAet_); + } + + if (content.DimseStatus != 0 /* success */) + { + throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " + "The request cannot be handled by remote AET: " + remoteAet_); + } + } + + Close(); + } + catch (OrthancException&) + { + Close(); + throw; + } + } }
--- a/Core/DicomNetworking/DicomUserConnection.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/DicomUserConnection.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -54,6 +54,13 @@ struct PImpl; boost::shared_ptr<PImpl> pimpl_; + enum Mode + { + Mode_Generic, + Mode_ReportStorageCommitment, + Mode_RequestStorageCommitment + }; + // Connection parameters std::string preferredTransferSyntax_; std::string modalityPreferredTransferSyntax_; @@ -68,7 +75,8 @@ void CheckIsOpen() const; - void SetupPresentationContexts(const std::string& preferredTransferSyntax); + void SetupPresentationContexts(Mode mode, + const std::string& preferredTransferSyntax); void MoveInternal(const std::string& targetAet, ResourceType level, @@ -80,6 +88,8 @@ void DefaultSetup(); + void OpenInternal(Mode mode); + public: DicomUserConnection(); @@ -138,7 +148,10 @@ void AddStorageSOPClass(const char* sop); - void Open(); + void Open() + { + OpenInternal(Mode_Generic); + } void Close(); @@ -146,33 +159,45 @@ bool Echo(); - void Store(const char* buffer, + void Store(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const char* buffer, size_t size, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); - void Store(const char* buffer, + void Store(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const char* buffer, size_t size) { - Store(buffer, size, "", 0); // Not a C-Move + Store(sopClassUid, sopInstanceUid, buffer, size, "", 0); // Not a C-Move } - void Store(const std::string& buffer, + void Store(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const std::string& buffer, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); - void Store(const std::string& buffer) + void Store(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const std::string& buffer) { - Store(buffer, "", 0); // Not a C-Move + Store(sopClassUid, sopInstanceUid, buffer, "", 0); // Not a C-Move } - void StoreFile(const std::string& path, + void StoreFile(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const std::string& path, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); - void StoreFile(const std::string& path) + void StoreFile(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + const std::string& path) { - StoreFile(path, "", 0); // Not a C-Move + StoreFile(sopClassUid, sopInstanceUid, path, "", 0); // Not a C-Move } void Find(DicomFindAnswers& result, @@ -213,5 +238,17 @@ bool IsSameAssociation(const std::string& localAet, const RemoteModalityParameters& remote) const; + + void ReportStorageCommitment( + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::vector<StorageCommitmentFailureReason>& failureReasons); + + // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier() + void RequestStorageCommitment( + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids); }; }
--- a/Core/DicomNetworking/IApplicationEntityFilter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IApplicationEntityFilter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IDicomConnectionManager.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IDicomConnectionManager.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IFindRequestHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IFindRequestHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IFindRequestHandlerFactory.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IFindRequestHandlerFactory.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IMoveRequestHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IMoveRequestHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IMoveRequestHandlerFactory.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IMoveRequestHandlerFactory.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IStorageCommitmentRequestHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> +#include <string> +#include <vector> + +namespace Orthanc +{ + class IStorageCommitmentRequestHandler : public boost::noncopyable + { + public: + virtual ~IStorageCommitmentRequestHandler() + { + } + + virtual void HandleRequest(const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; + + virtual void HandleReport(const std::string& transactionUid, + const std::vector<std::string>& successSopClassUids, + const std::vector<std::string>& successSopInstanceUids, + const std::vector<std::string>& failedSopClassUids, + const std::vector<std::string>& failedSopInstanceUids, + const std::vector<StorageCommitmentFailureReason>& failureReasons, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IStorageCommitmentRequestHandlerFactory.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "IStorageCommitmentRequestHandler.h" + +namespace Orthanc +{ + class IStorageCommitmentRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IStorageCommitmentRequestHandlerFactory() + { + } + + virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler() = 0; + }; +}
--- a/Core/DicomNetworking/IStoreRequestHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IStoreRequestHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IStoreRequestHandlerFactory.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IStoreRequestHandlerFactory.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IWorklistRequestHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IWorklistRequestHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IWorklistRequestHandlerFactory.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/IWorklistRequestHandlerFactory.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/Internals/CommandDispatcher.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -90,11 +90,15 @@ #include "FindScp.h" #include "StoreScp.h" #include "MoveScp.h" +#include "../../Compatibility.h" #include "../../Toolbox.h" #include "../../Logging.h" +#include "../../OrthancException.h" +#include <dcmtk/dcmdata/dcdeftag.h> /* for storage commitment */ +#include <dcmtk/dcmdata/dcsequen.h> /* for class DcmSequenceOfItems */ +#include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ -#include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ #include <boost/lexical_cast.hpp> @@ -271,33 +275,6 @@ OFString sprofile; OFString temp_str; - std::vector<const char*> knownAbstractSyntaxes; - - // For C-STORE - if (server.HasStoreRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); - } - - // For C-FIND - if (server.HasFindRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - } - - if (server.HasWorklistRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel); - } - - // For C-MOVE - if (server.HasMoveRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); - } - cond = ASC_receiveAssociation(net, &assoc, /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, NULL, NULL, @@ -361,133 +338,206 @@ << " on IP " << remoteIp; - std::vector<const char*> transferSyntaxes; - - // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 - transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); - transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); - transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); - - // New transfer syntaxes supported since Orthanc 0.7.2 - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated)) { - transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); - } + /* accept the abstract syntaxes for C-ECHO, C-FIND, C-MOVE, + and storage commitment, if presented */ - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg)) - { - transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); - } + std::vector<const char*> genericTransferSyntaxes; + genericTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); + genericTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); + genericTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000)) - { - transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); - } + std::vector<const char*> knownAbstractSyntaxes; - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) - { - transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); - transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); - } + // For C-ECHO (always enabled since Orthanc 1.6.0; in earlier + // versions, only enabled if C-STORE was also enabled) + knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip)) - { - transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); - transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2)) - { - transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); - transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); - } + // For C-FIND + if (server.HasFindRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); + knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); + } - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) - { - transferSyntaxes.push_back(UID_RLELosslessTransferSyntax); - } - - /* accept the Verification SOP Class if presented */ - cond = ASC_acceptContextsWithPreferredTransferSyntaxes( - assoc->params, - &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), - &transferSyntaxes[0], transferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } + if (server.HasWorklistRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel); + } - /* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */ - size_t count = 0; - while (dcmAllStorageSOPClassUIDs[count] != NULL) - { - count++; - } + // For C-MOVE + if (server.HasMoveRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); + knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); + } -#if DCMTK_VERSION_NUMBER >= 362 - // The global variable "numberOfDcmAllStorageSOPClassUIDs" is - // only published if DCMTK >= 3.6.2: - // https://bitbucket.org/sjodogne/orthanc/issues/137 - assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs); -#endif - - cond = ASC_acceptContextsWithPreferredTransferSyntaxes( - assoc->params, - dcmAllStorageSOPClassUIDs, count, - &transferSyntaxes[0], transferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet)) - { - /* - * Promiscous mode is enabled: Accept everything not known not - * to be a storage SOP class. - **/ - cond = acceptUnknownContextsWithPreferredTransferSyntaxes( - assoc->params, &transferSyntaxes[0], transferSyntaxes.size(), ASC_SC_ROLE_DEFAULT); + cond = ASC_acceptContextsWithPreferredTransferSyntaxes( + assoc->params, + &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), + &genericTransferSyntaxes[0], genericTransferSyntaxes.size()); if (cond.bad()) { LOG(INFO) << cond.text(); AssociationCleanup(assoc); return NULL; } + + + /* storage commitment support, new in Orthanc 1.6.0 */ + if (server.HasStorageCommitmentRequestHandlerFactory()) + { + /** + * "ASC_SC_ROLE_SCUSCP": The "SCU" role is needed to accept + * remote storage commitment requests, and the "SCP" role is + * needed to receive storage commitments answers. + **/ + const char* as[1] = { UID_StorageCommitmentPushModelSOPClass }; + cond = ASC_acceptContextsWithPreferredTransferSyntaxes( + assoc->params, as, 1, + &genericTransferSyntaxes[0], genericTransferSyntaxes.size(), ASC_SC_ROLE_SCUSCP); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + } + } + + + { + /* accept the abstract syntaxes for C-STORE, if presented */ + + std::vector<const char*> storageTransferSyntaxes; + + // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 + storageTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); + storageTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); + storageTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); + + // New transfer syntaxes supported since Orthanc 0.7.2 + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated)) + { + storageTransferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg)) + { + storageTransferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000)) + { + storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) + { + storageTransferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); + storageTransferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip)) + { + storageTransferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); + storageTransferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2)) + { + storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); + storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); + } + +#if DCMTK_VERSION_NUMBER >= 361 + // New in Orthanc 1.6.0 + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg4)) + { + storageTransferSyntaxes.push_back(UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax); + storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_1TransferSyntax); + storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax); + storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax); + storageTransferSyntaxes.push_back(UID_MPEG4StereoHighProfileLevel4_2TransferSyntax); + } +#endif + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) + { + storageTransferSyntaxes.push_back(UID_RLELosslessTransferSyntax); + } + + /* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */ + size_t count = 0; + while (dcmAllStorageSOPClassUIDs[count] != NULL) + { + count++; + } + +#if DCMTK_VERSION_NUMBER >= 362 + // The global variable "numberOfDcmAllStorageSOPClassUIDs" is + // only published if DCMTK >= 3.6.2: + // https://bitbucket.org/sjodogne/orthanc/issues/137 + assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs); +#endif + + cond = ASC_acceptContextsWithPreferredTransferSyntaxes( + assoc->params, + dcmAllStorageSOPClassUIDs, count, + &storageTransferSyntaxes[0], storageTransferSyntaxes.size()); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet)) + { + /* + * Promiscous mode is enabled: Accept everything not known not + * to be a storage SOP class. + **/ + cond = acceptUnknownContextsWithPreferredTransferSyntaxes( + assoc->params, &storageTransferSyntaxes[0], storageTransferSyntaxes.size(), ASC_SC_ROLE_DEFAULT); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + } } /* set our app title */ @@ -689,6 +739,16 @@ supported = true; break; + case DIMSE_N_ACTION_RQ: + request = DicomRequestType_NAction; + supported = true; + break; + + case DIMSE_N_EVENT_REPORT_RQ: + request = DicomRequestType_NEventReport; + supported = true; + break; + default: // we cannot handle this kind of message cond = DIMSE_BADCOMMANDTYPE; @@ -725,12 +785,12 @@ case DicomRequestType_Store: if (server_.HasStoreRequestHandlerFactory()) // Should always be true { - std::auto_ptr<IStoreRequestHandler> handler + std::unique_ptr<IStoreRequestHandler> handler (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); if (handler.get() != NULL) { - cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_); + cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_, associationTimeout_); } } break; @@ -738,12 +798,12 @@ case DicomRequestType_Move: if (server_.HasMoveRequestHandlerFactory()) // Should always be true { - std::auto_ptr<IMoveRequestHandler> handler + std::unique_ptr<IMoveRequestHandler> handler (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); if (handler.get() != NULL) { - cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_); + cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_, associationTimeout_); } } break; @@ -752,13 +812,13 @@ if (server_.HasFindRequestHandlerFactory() || // Should always be true server_.HasWorklistRequestHandlerFactory()) { - std::auto_ptr<IFindRequestHandler> findHandler; + std::unique_ptr<IFindRequestHandler> findHandler; if (server_.HasFindRequestHandlerFactory()) { findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); } - std::auto_ptr<IWorklistRequestHandler> worklistHandler; + std::unique_ptr<IWorklistRequestHandler> worklistHandler; if (server_.HasWorklistRequestHandlerFactory()) { worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler()); @@ -766,10 +826,18 @@ cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(), findHandler.get(), worklistHandler.get(), - remoteIp_, remoteAet_, calledAet_); + remoteIp_, remoteAet_, calledAet_, associationTimeout_); } break; + case DicomRequestType_NAction: + cond = NActionScp(&msg, presID); + break; + + case DicomRequestType_NEventReport: + cond = NEventReportScp(&msg, presID); + break; + default: // Should never happen break; @@ -823,5 +891,379 @@ } return cond; } + + + static DcmDataset* ReadDataset(T_ASC_Association* assoc, + const char* errorMessage, + int timeout) + { + DcmDataset *tmp = NULL; + T_ASC_PresentationContextID presIdData; + + OFCondition cond = DIMSE_receiveDataSetInMemory( + assoc, (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, + &presIdData, &tmp, NULL, NULL); + if (!cond.good() || + tmp == NULL) + { + throw OrthancException(ErrorCode_NetworkProtocol, errorMessage); + } + + return tmp; + } + + + static std::string ReadString(DcmDataset& dataset, + const DcmTagKey& tag) + { + const char* s = NULL; + if (!dataset.findAndGetString(tag, s).good() || + s == NULL) + { + char buf[64]; + sprintf(buf, "Missing mandatory tag in dataset: (%04X,%04X)", + tag.getGroup(), tag.getElement()); + throw OrthancException(ErrorCode_NetworkProtocol, buf); + } + + return std::string(s); + } + + + static void ReadSopSequence( + std::vector<std::string>& sopClassUids, + std::vector<std::string>& sopInstanceUids, + std::vector<StorageCommitmentFailureReason>* failureReasons, // Can be NULL + DcmDataset& dataset, + const DcmTagKey& tag, + bool mandatory) + { + sopClassUids.clear(); + sopInstanceUids.clear(); + + if (failureReasons) + { + failureReasons->clear(); + } + + DcmSequenceOfItems* sequence = NULL; + if (!dataset.findAndGetSequence(tag, sequence).good() || + sequence == NULL) + { + if (mandatory) + { + char buf[64]; + sprintf(buf, "Missing mandatory sequence in dataset: (%04X,%04X)", + tag.getGroup(), tag.getElement()); + throw OrthancException(ErrorCode_NetworkProtocol, buf); + } + else + { + return; + } + } + + sopClassUids.reserve(sequence->card()); + sopInstanceUids.reserve(sequence->card()); + + if (failureReasons) + { + failureReasons->reserve(sequence->card()); + } + + for (unsigned long i = 0; i < sequence->card(); i++) + { + const char* a = NULL; + const char* b = NULL; + if (!sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPClassUID, a).good() || + !sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPInstanceUID, b).good() || + a == NULL || + b == NULL) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Missing Referenced SOP Class/Instance UID " + "in storage commitment dataset"); + } + + sopClassUids.push_back(a); + sopInstanceUids.push_back(b); + + if (failureReasons != NULL) + { + Uint16 reason; + if (!sequence->getItem(i)->findAndGetUint16(DCM_FailureReason, reason).good()) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Missing Failure Reason (0008,1197) " + "in storage commitment dataset"); + } + + failureReasons->push_back(static_cast<StorageCommitmentFailureReason>(reason)); + } + } + } + + + OFCondition CommandDispatcher::NActionScp(T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID) + { + /** + * Starting with Orthanc 1.6.0, only storage commitment is + * supported with DICOM N-ACTION. This corresponds to the case + * where "Action Type ID" equals "1". + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 + **/ + + if (msg->CommandField != DIMSE_N_ACTION_RQ /* value == 304 == 0x0130 */ || + !server_.HasStorageCommitmentRequestHandlerFactory()) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * Check that the storage commitment request is correctly formatted. + **/ + + const T_DIMSE_N_ActionRQ& request = msg->msg.NActionRQ; + + if (request.ActionTypeID != 1) + { + throw OrthancException(ErrorCode_NotImplemented, + "Only storage commitment is implemented for DICOM N-ACTION SCP"); + } + + if (std::string(request.RequestedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || + std::string(request.RequestedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Unexpected incoming SOP class or instance UID for storage commitment"); + } + + if (request.DataSetType != DIMSE_DATASET_PRESENT) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Incoming storage commitment request without a dataset"); + } + + + /** + * Extract the DICOM dataset that is associated with the DIMSE + * message. The content of this dataset is documented in "Table + * J.3-1. Storage Commitment Request - Action Information": + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html#table_J.3-1 + **/ + + std::unique_ptr<DcmDataset> dataset( + ReadDataset(assoc_, "Cannot read the dataset in N-ACTION SCP", associationTimeout_)); + + std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); + + std::vector<std::string> sopClassUid, sopInstanceUid; + ReadSopSequence(sopClassUid, sopInstanceUid, NULL, + *dataset, DCM_ReferencedSOPSequence, true /* mandatory */); + + LOG(INFO) << "Incoming storage commitment request, with transaction UID: " << transactionUid; + + for (size_t i = 0; i < sopClassUid.size(); i++) + { + LOG(INFO) << " (" << (i + 1) << "/" << sopClassUid.size() + << ") queried SOP Class/Instance UID: " + << sopClassUid[i] << " / " << sopInstanceUid[i]; + } + + + /** + * Call the Orthanc handler. The list of available DIMSE status + * codes can be found at: + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10 + **/ + + DIC_US dimseStatus; + + try + { + std::unique_ptr<IStorageCommitmentRequestHandler> handler + (server_.GetStorageCommitmentRequestHandlerFactory(). + ConstructStorageCommitmentRequestHandler()); + + handler->HandleRequest(transactionUid, sopClassUid, sopInstanceUid, + remoteIp_, remoteAet_, calledAet_); + + dimseStatus = 0; // Success + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error while processing an incoming storage commitment request: " << e.What(); + + // Code 0x0110 - "General failure in processing the operation was encountered" + dimseStatus = STATUS_N_ProcessingFailure; + } + + + /** + * Send the DIMSE status back to the SCU. + **/ + + { + T_DIMSE_Message response; + memset(&response, 0, sizeof(response)); + response.CommandField = DIMSE_N_ACTION_RSP; + + T_DIMSE_N_ActionRSP& content = response.msg.NActionRSP; + content.MessageIDBeingRespondedTo = request.MessageID; + strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); + content.DimseStatus = dimseStatus; + strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); + content.ActionTypeID = 0; // Not present, as "O_NACTION_ACTIONTYPEID" not set in "opts" + content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response + content.opts = O_NACTION_AFFECTEDSOPCLASSUID | O_NACTION_AFFECTEDSOPINSTANCEUID; + + return DIMSE_sendMessageUsingMemoryData( + assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, + NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); + } + } + + + OFCondition CommandDispatcher::NEventReportScp(T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID) + { + /** + * Starting with Orthanc 1.6.0, handling N-EVENT-REPORT for + * storage commitment. + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 + **/ + + if (msg->CommandField != DIMSE_N_EVENT_REPORT_RQ /* value == 256 == 0x0100 */ || + !server_.HasStorageCommitmentRequestHandlerFactory()) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * Check that the storage commitment report is correctly formatted. + **/ + + const T_DIMSE_N_EventReportRQ& report = msg->msg.NEventReportRQ; + + if (report.EventTypeID != 1 /* successful */ && + report.EventTypeID != 2 /* failures exist */) + { + throw OrthancException(ErrorCode_NotImplemented, + "Unknown event for DICOM N-EVENT-REPORT SCP"); + } + + if (std::string(report.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || + std::string(report.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Unexpected incoming SOP class or instance UID for storage commitment"); + } + + if (report.DataSetType != DIMSE_DATASET_PRESENT) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Incoming storage commitment report without a dataset"); + } + + + /** + * Extract the DICOM dataset that is associated with the DIMSE + * message. The content of this dataset is documented in "Table + * J.3-2. Storage Commitment Result - Event Information": + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html#table_J.3-2 + **/ + + std::unique_ptr<DcmDataset> dataset( + ReadDataset(assoc_, "Cannot read the dataset in N-EVENT-REPORT SCP", associationTimeout_)); + + std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); + + std::vector<std::string> successSopClassUid, successSopInstanceUid; + ReadSopSequence(successSopClassUid, successSopInstanceUid, NULL, + *dataset, DCM_ReferencedSOPSequence, + (report.EventTypeID == 1) /* mandatory in the case of success */); + + std::vector<std::string> failedSopClassUid, failedSopInstanceUid; + std::vector<StorageCommitmentFailureReason> failureReasons; + + if (report.EventTypeID == 2 /* failures exist */) + { + ReadSopSequence(failedSopClassUid, failedSopInstanceUid, &failureReasons, + *dataset, DCM_FailedSOPSequence, true); + } + + LOG(INFO) << "Incoming storage commitment report, with transaction UID: " << transactionUid; + + for (size_t i = 0; i < successSopClassUid.size(); i++) + { + LOG(INFO) << " (success " << (i + 1) << "/" << successSopClassUid.size() + << ") SOP Class/Instance UID: " + << successSopClassUid[i] << " / " << successSopInstanceUid[i]; + } + + for (size_t i = 0; i < failedSopClassUid.size(); i++) + { + LOG(INFO) << " (failure " << (i + 1) << "/" << failedSopClassUid.size() + << ") SOP Class/Instance UID: " + << failedSopClassUid[i] << " / " << failedSopInstanceUid[i]; + } + + /** + * Call the Orthanc handler. The list of available DIMSE status + * codes can be found at: + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10 + **/ + + DIC_US dimseStatus; + + try + { + std::unique_ptr<IStorageCommitmentRequestHandler> handler + (server_.GetStorageCommitmentRequestHandlerFactory(). + ConstructStorageCommitmentRequestHandler()); + + handler->HandleReport(transactionUid, successSopClassUid, successSopInstanceUid, + failedSopClassUid, failedSopInstanceUid, failureReasons, + remoteIp_, remoteAet_, calledAet_); + + dimseStatus = 0; // Success + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error while processing an incoming storage commitment report: " << e.What(); + + // Code 0x0110 - "General failure in processing the operation was encountered" + dimseStatus = STATUS_N_ProcessingFailure; + } + + + /** + * Send the DIMSE status back to the SCU. + **/ + + { + T_DIMSE_Message response; + memset(&response, 0, sizeof(response)); + response.CommandField = DIMSE_N_EVENT_REPORT_RSP; + + T_DIMSE_N_EventReportRSP& content = response.msg.NEventReportRSP; + content.MessageIDBeingRespondedTo = report.MessageID; + strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); + content.DimseStatus = dimseStatus; + strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); + content.EventTypeID = 0; // Not present, as "O_NEVENTREPORT_EVENTTYPEID" not set in "opts" + content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response + content.opts = O_NEVENTREPORT_AFFECTEDSOPCLASSUID | O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID; + + return DIMSE_sendMessageUsingMemoryData( + assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, + NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); + } + } } }
--- a/Core/DicomNetworking/Internals/CommandDispatcher.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -56,6 +56,12 @@ std::string calledAet_; IApplicationEntityFilter* filter_; + OFCondition NActionScp(T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID); + + OFCondition NEventReportScp(T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID); + public: CommandDispatcher(const DicomServer& server, T_ASC_Association* assoc, @@ -69,11 +75,11 @@ virtual bool Step(); }; - OFCondition EchoScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID); - CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net); + + OFCondition EchoScp(T_ASC_Association* assoc, + T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID); } }
--- a/Core/DicomNetworking/Internals/FindScp.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/FindScp.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -346,7 +346,8 @@ IWorklistRequestHandler* worklistHandler, const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet) + const std::string& calledAet, + int timeout) { FindScpData data; data.modalities_ = &modalities; @@ -359,8 +360,8 @@ OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, FindScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); + /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), + /*opt_dimse_timeout*/ timeout); // if some error occured, dump corresponding information and remove the outfile if necessary if (cond.bad())
--- a/Core/DicomNetworking/Internals/FindScp.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/FindScp.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -49,6 +49,7 @@ IWorklistRequestHandler* worklistHandler, // can be NULL const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet); + const std::string& calledAet, + int timeout); } }
--- a/Core/DicomNetworking/Internals/MoveScp.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/MoveScp.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -114,7 +114,7 @@ unsigned int subOperationCount_; unsigned int failureCount_; unsigned int warningCount_; - std::auto_ptr<IMoveRequestIterator> iterator_; + std::unique_ptr<IMoveRequestIterator> iterator_; const std::string* remoteIp_; const std::string* remoteAet_; const std::string* calledAet_; @@ -272,7 +272,8 @@ IMoveRequestHandler& handler, const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet) + const std::string& calledAet, + int timeout) { MoveScpData data; data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination); @@ -284,8 +285,8 @@ OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, MoveScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); + /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), + /*opt_dimse_timeout*/ timeout); // if some error occured, dump corresponding information and remove the outfile if necessary if (cond.bad())
--- a/Core/DicomNetworking/Internals/MoveScp.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/MoveScp.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -47,6 +47,7 @@ IMoveRequestHandler& handler, const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet); + const std::string& calledAet, + int timeout); } }
--- a/Core/DicomNetworking/Internals/StoreScp.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/StoreScp.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -251,7 +251,8 @@ T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, IStoreRequestHandler& handler, - const std::string& remoteIp) + const std::string& remoteIp, + int timeout) { OFCondition cond = EC_Normal; T_DIMSE_C_StoreRQ *req; @@ -294,8 +295,8 @@ cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset, storeScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); + /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), + /*opt_dimse_timeout*/ timeout); // if some error occured, dump corresponding information and remove the outfile if necessary if (cond.bad())
--- a/Core/DicomNetworking/Internals/StoreScp.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/Internals/StoreScp.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -45,6 +45,7 @@ T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, IStoreRequestHandler& handler, - const std::string& remoteIp); + const std::string& remoteIp, + int timeout); } }
--- a/Core/DicomNetworking/RemoteModalityParameters.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -50,6 +50,9 @@ static const char* KEY_ALLOW_GET = "AllowGet"; static const char* KEY_ALLOW_MOVE = "AllowMove"; static const char* KEY_ALLOW_STORE = "AllowStore"; +static const char* KEY_ALLOW_N_ACTION = "AllowNAction"; +static const char* KEY_ALLOW_N_EVENT_REPORT = "AllowEventReport"; +static const char* KEY_ALLOW_STORAGE_COMMITMENT = "AllowStorageCommitment"; static const char* KEY_HOST = "Host"; static const char* KEY_PREFERRED_TRANSFER_SYNTAX = "PreferredTransferSyntax"; static const char* KEY_MANUFACTURER = "Manufacturer"; @@ -70,6 +73,8 @@ allowFind_ = true; allowMove_ = true; allowGet_ = true; + allowNAction_ = true; // For storage commitment + allowNEventReport_ = true; // For storage commitment } @@ -171,7 +176,11 @@ aet_ = SerializationToolbox::ReadString(serialized, KEY_AET); host_ = SerializationToolbox::ReadString(serialized, KEY_HOST); - preferredTransferSyntax_ = SerializationToolbox::ReadString(serialized, KEY_PREFERRED_TRANSFER_SYNTAX); + + if (serialized.isMember(KEY_PREFERRED_TRANSFER_SYNTAX)) + { + preferredTransferSyntax_ = SerializationToolbox::ReadString(serialized, KEY_PREFERRED_TRANSFER_SYNTAX); + } if (serialized.isMember(KEY_PORT)) { @@ -216,6 +225,23 @@ { allowMove_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_MOVE); } + + if (serialized.isMember(KEY_ALLOW_N_ACTION)) + { + allowNAction_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_ACTION); + } + + if (serialized.isMember(KEY_ALLOW_N_EVENT_REPORT)) + { + allowNEventReport_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_EVENT_REPORT); + } + + if (serialized.isMember(KEY_ALLOW_STORAGE_COMMITMENT)) + { + bool allow = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_STORAGE_COMMITMENT); + allowNAction_ = allow; + allowNEventReport_ = allow; + } } @@ -238,6 +264,12 @@ case DicomRequestType_Store: return allowStore_; + case DicomRequestType_NAction: + return allowNAction_; + + case DicomRequestType_NEventReport: + return allowNEventReport_; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -269,6 +301,14 @@ allowStore_ = allowed; break; + case DicomRequestType_NAction: + allowNAction_ = allowed; + break; + + case DicomRequestType_NEventReport: + allowNEventReport_ = allowed; + break; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -281,7 +321,9 @@ !allowStore_ || !allowFind_ || !allowGet_ || - !allowMove_); + !allowMove_ || + !allowNAction_ || + !allowNEventReport_); } @@ -302,6 +344,8 @@ target[KEY_ALLOW_FIND] = allowFind_; target[KEY_ALLOW_GET] = allowGet_; target[KEY_ALLOW_MOVE] = allowMove_; + target[KEY_ALLOW_N_ACTION] = allowNAction_; + target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_; } else {
--- a/Core/DicomNetworking/RemoteModalityParameters.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -54,6 +54,9 @@ bool allowFind_; bool allowMove_; bool allowGet_; + bool allowNAction_; + bool allowNEventReport_; + void Clear(); void UnserializeArray(const Json::Value& serialized);
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -63,6 +63,8 @@ #else +#include "../Compatibility.h" + #include <boost/date_time/posix_time/posix_time.hpp> namespace Orthanc @@ -72,9 +74,9 @@ private: class Resource; - std::auto_ptr<DicomUserConnection> connection_; - boost::posix_time::ptime lastUse_; - boost::posix_time::time_duration timeout_; + std::unique_ptr<DicomUserConnection> connection_; + boost::posix_time::ptime lastUse_; + boost::posix_time::time_duration timeout_; void Touch();
--- a/Core/DicomParsing/DicomDirWriter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/DicomDirWriter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -104,6 +104,7 @@ #include "FromDcmtkBridge.h" #include "ToDcmtkBridge.h" +#include "../Compatibility.h" #include "../Logging.h" #include "../OrthancException.h" #include "../TemporaryFile.h" @@ -132,7 +133,7 @@ std::string fileSetId_; bool extendedSopClass_; TemporaryFile file_; - std::auto_ptr<DcmDicomDir> dir_; + std::unique_ptr<DcmDicomDir> dir_; typedef std::pair<ResourceType, std::string> IndexKey; typedef std::map<IndexKey, DcmDirectoryRecord* > Index; @@ -466,7 +467,7 @@ return false; // Already existing } - std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); + std::unique_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); switch (level) {
--- a/Core/DicomParsing/DicomDirWriter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/DicomDirWriter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/DicomModification.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/DicomModification.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,13 +34,14 @@ #include "../PrecompiledHeaders.h" #include "DicomModification.h" +#include "../Compatibility.h" #include "../Logging.h" #include "../OrthancException.h" #include "../SerializationToolbox.h" #include "FromDcmtkBridge.h" #include "ITagVisitor.h" -#include <memory> // For std::auto_ptr +#include <memory> // For std::unique_ptr static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 = @@ -317,7 +318,7 @@ void DicomModification::MapDicomTags(ParsedDicomFile& dicom, ResourceType level) { - std::auto_ptr<DicomTag> tag; + std::unique_ptr<DicomTag> tag; switch (level) { @@ -347,7 +348,7 @@ dicom.Replace(*tag, mapped, false /* don't try and decode data URI scheme for UIDs */, - DicomReplaceMode_InsertIfAbsent); + DicomReplaceMode_InsertIfAbsent, privateCreator_); } @@ -359,6 +360,7 @@ keepSeriesInstanceUid_(false), updateReferencedRelationships_(true), isAnonymization_(false), + //privateCreator_("PrivateCreator"), identifierGenerator_(NULL) { } @@ -1067,7 +1069,8 @@ for (Replacements::const_iterator it = replacements_.begin(); it != replacements_.end(); ++it) { - toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent); + toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, + DicomReplaceMode_InsertIfAbsent, privateCreator_); } // (6) Update the DICOM identifiers @@ -1262,6 +1265,12 @@ { ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); } + + // New in Orthanc 1.6.0 + if (request.isMember("PrivateCreator")) + { + privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); + } } @@ -1316,6 +1325,12 @@ patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); + + // New in Orthanc 1.6.0 + if (request.isMember("PrivateCreator")) + { + privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); + } } @@ -1336,6 +1351,7 @@ static const char* MAP_STUDIES = "MapStudies"; static const char* MAP_SERIES = "MapSeries"; static const char* MAP_INSTANCES = "MapInstances"; + static const char* PRIVATE_CREATOR = "PrivateCreator"; // New in Orthanc 1.6.0 void DicomModification::Serialize(Json::Value& value) const { @@ -1353,6 +1369,7 @@ value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; value[IS_ANONYMIZATION] = isAnonymization_; + value[PRIVATE_CREATOR] = privateCreator_; SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); @@ -1451,6 +1468,11 @@ (serialized, UPDATE_REFERENCED_RELATIONSHIPS); isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); + if (serialized.isMember(PRIVATE_CREATOR)) + { + privateCreator_ = SerializationToolbox::ReadString(serialized, PRIVATE_CREATOR); + } + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP);
--- a/Core/DicomParsing/DicomModification.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/DicomModification.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -86,6 +86,7 @@ bool updateReferencedRelationships_; bool isAnonymization_; DicomMap currentSource_; + std::string privateCreator_; IDicomIdentifierGenerator* identifierGenerator_; @@ -185,5 +186,15 @@ } void Serialize(Json::Value& value) const; + + void SetPrivateCreator(const std::string& privateCreator) + { + privateCreator_ = privateCreator; + } + + const std::string& GetPrivateCreator() + { + return privateCreator_; + } }; }
--- a/Core/DicomParsing/DicomWebJsonVisitor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/DicomWebJsonVisitor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/DicomWebJsonVisitor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/DicomWebJsonVisitor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/FromDcmtkBridge.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -47,6 +47,7 @@ #include "FromDcmtkBridge.h" #include "ToDcmtkBridge.h" +#include "../Compatibility.h" #include "../Logging.h" #include "../Toolbox.h" #include "../OrthancException.h" @@ -67,10 +68,11 @@ #include <dcmtk/dcmdata/dcdicent.h> #include <dcmtk/dcmdata/dcdict.h> #include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcistrmb.h> #include <dcmtk/dcmdata/dcostrmb.h> #include <dcmtk/dcmdata/dcpixel.h> #include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcistrmb.h> +#include <dcmtk/dcmdata/dcxfer.h> #include <dcmtk/dcmdata/dcvrae.h> #include <dcmtk/dcmdata/dcvras.h> @@ -106,15 +108,31 @@ #if ORTHANC_ENABLE_DCMTK_JPEG == 1 # include <dcmtk/dcmjpeg/djdecode.h> +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include <dcmtk/dcmjpeg/djencode.h> +# endif #endif #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 # include <dcmtk/dcmjpls/djdecode.h> +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include <dcmtk/dcmjpls/djencode.h> +# endif #endif namespace Orthanc { + static bool IsBinaryTag(const DcmTag& key) + { + return (key.isUnknownVR() || + key.getEVR() == EVR_OB || + key.getEVR() == EVR_OW || + key.getEVR() == EVR_UN || + key.getEVR() == EVR_ox); + } + + #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, EmbeddedResources::FileResourceId resource) @@ -177,18 +195,18 @@ }; -#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter) \ - \ - struct converter \ - { \ - typedef cType CType; \ - \ - static bool Apply(CType& result, \ - DcmElement& element, \ - size_t i) \ - { \ +#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter) \ + \ + struct converter \ + { \ + typedef cType CType; \ + \ + static bool Apply(CType& result, \ + DcmElement& element, \ + size_t i) \ + { \ return dynamic_cast<dcmtkType&>(element).getter(result, i).good(); \ - } \ + } \ }; DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32) @@ -341,7 +359,7 @@ << name << " (multiplicity: " << minMultiplicity << "-" << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")"; - std::auto_ptr<DcmDictEntry> entry; + std::unique_ptr<DcmDictEntry> entry; if (privateCreator.empty()) { if (tag.GetGroup() % 2 == 1) @@ -456,10 +474,9 @@ void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, DcmItem& dataset, unsigned int maxStringLength, - Encoding defaultEncoding) + Encoding defaultEncoding, + const std::set<DicomTag>& ignoreTagLength) { - std::set<DicomTag> ignoreTagLength; - bool hasCodeExtensions; Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); @@ -469,10 +486,10 @@ DcmElement* element = dataset.getElement(i); if (element && element->isLeaf()) { - target.SetValue(element->getTag().getGTag(), - element->getTag().getETag(), - ConvertLeafElement(*element, DicomToJsonFlags_Default, - maxStringLength, encoding, hasCodeExtensions, ignoreTagLength)); + target.SetValueInternal(element->getTag().getGTag(), + element->getTag().getETag(), + ConvertLeafElement(*element, DicomToJsonFlags_Default, + maxStringLength, encoding, hasCodeExtensions, ignoreTagLength)); } } } @@ -876,7 +893,7 @@ if (element.isLeaf()) { // The "0" below lets "LeafValueToJson()" take care of "TooLong" values - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength)); if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) @@ -939,18 +956,13 @@ if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) { DictionaryLocker locker; - if (locker->findEntry(element->getTag(), NULL) == NULL) + if (locker->findEntry(element->getTag(), element->getTag().getPrivateCreator()) == NULL) { continue; } } - DcmEVR evr = element->getTag().getEVR(); - if (evr == EVR_OB || - evr == EVR_OF || - evr == EVR_OW || - evr == EVR_UN || - evr == EVR_ox) + if (IsBinaryTag(element->getTag())) { // This is a binary tag if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || @@ -1118,8 +1130,8 @@ result.clear(); - for (DicomMap::Map::const_iterator - it = values.map_.begin(); it != values.map_.end(); ++it) + for (DicomMap::Content::const_iterator + it = values.content_.begin(); it != values.content_.end(); ++it) { // TODO Inject PrivateCreator if some is available in the DicomMap? const std::string tagName = GetTagName(it->first, ""); @@ -1372,189 +1384,49 @@ } - static bool IsBinaryTag(const DcmTag& key) + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag, + const std::string& privateCreator) { - return (key.isUnknownVR() || -#if DCMTK_VERSION_NUMBER >= 361 - key.getEVR() == EVR_OD || -#endif - + if (tag.IsPrivate() && + privateCreator.empty()) + { + // This solves issue 140 (Modifying private tags with REST API + // changes VR from LO to UN) + // https://bitbucket.org/sjodogne/orthanc/issues/140 + LOG(WARNING) << "Private creator should not be empty while creating a private tag: " << tag.Format(); + } + #if DCMTK_VERSION_NUMBER >= 362 - key.getEVR() == EVR_OL || -#endif - key.getEVR() == EVR_OB || - key.getEVR() == EVR_OF || - key.getEVR() == EVR_OW || - key.getEVR() == EVR_UN || - key.getEVR() == EVR_ox); - } - - - DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) - { DcmTag key(tag.GetGroup(), tag.GetElement()); - - if (tag.IsPrivate() || - IsBinaryTag(key)) + if (tag.IsPrivate()) + { + return DcmItem::newDicomElement(key, privateCreator.c_str()); + } + else { + return DcmItem::newDicomElement(key, NULL); + } + +#else + DcmTag key(tag.GetGroup(), tag.GetElement()); + if (tag.IsPrivate()) + { + // https://forum.dcmtk.org/viewtopic.php?t=4527 + LOG(WARNING) << "You are using DCMTK <= 3.6.1: All the private tags " + "are considered as having a binary value representation"; + key.setPrivateCreator(privateCreator.c_str()); return new DcmOtherByteOtherWord(key); } - - switch (key.getEVR()) + else { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * Binary types, handled above - **/ - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_OD: -#endif - -#if DCMTK_VERSION_NUMBER >= 362 - case EVR_OL: -#endif - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_UN: // unknown value representation - case EVR_ox: // OB or OW depending on context - throw OrthancException(ErrorCode_InternalError); - - - /** - * String types. - * http://support.dcmtk.org/docs/classDcmByteString.html - **/ - - case EVR_AS: // age string - return new DcmAgeString(key); - - case EVR_AE: // application entity title - return new DcmApplicationEntity(key); - - case EVR_CS: // code string - return new DcmCodeString(key); - - case EVR_DA: // date string - return new DcmDate(key); - - case EVR_DT: // date time string - return new DcmDateTime(key); - - case EVR_DS: // decimal string - return new DcmDecimalString(key); - - case EVR_IS: // integer string - return new DcmIntegerString(key); - - case EVR_TM: // time string - return new DcmTime(key); - - case EVR_UI: // unique identifier - return new DcmUniqueIdentifier(key); - - case EVR_ST: // short text - return new DcmShortText(key); - - case EVR_LO: // long string - return new DcmLongString(key); - - case EVR_LT: // long text - return new DcmLongText(key); - - case EVR_UT: // unlimited text - return new DcmUnlimitedText(key); - - case EVR_SH: // short string - return new DcmShortString(key); - - case EVR_PN: // person name - return new DcmPersonName(key); - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_UC: // unlimited characters - return new DcmUnlimitedCharacters(key); -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_UR: // URI/URL - return new DcmUniversalResourceIdentifierOrLocator(key); -#endif - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - return new DcmSignedLong(key); - - case EVR_SS: // signed short - return new DcmSignedShort(key); - - case EVR_UL: // unsigned long - return new DcmUnsignedLong(key); - - case EVR_US: // unsigned short - return new DcmUnsignedShort(key); - - case EVR_FL: // float single-precision - return new DcmFloatingPointSingle(key); - - case EVR_FD: // float double-precision - return new DcmFloatingPointDouble(key); - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * TODO - **/ - - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - - /** - * Internal to DCMTK. - **/ - - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - default: - break; + return newDicomElement(key); } - - throw OrthancException(ErrorCode_InternalError); +#endif } void FromDcmtkBridge::FillElementWithString(DcmElement& element, - const DicomTag& tag, const std::string& utf8Value, bool decodeDataUriScheme, Encoding dicomEncoding) @@ -1579,14 +1451,11 @@ decoded = &binary; } - DcmTag key(tag.GetGroup(), tag.GetElement()); - - if (tag.IsPrivate() || - IsBinaryTag(key)) + if (IsBinaryTag(element.getTag())) { bool ok; - switch (key.getEVR()) + switch (element.getTag().getEVR()) { case EVR_OW: if (decoded->size() % sizeof(Uint16) != 0) @@ -1620,7 +1489,7 @@ try { - switch (key.getEVR()) + switch (element.getTag().getEVR()) { // http://support.dcmtk.org/docs/dcvr_8h-source.html @@ -1629,7 +1498,6 @@ **/ case EVR_OB: // other byte - case EVR_OF: // other float case EVR_OW: // other word case EVR_AT: // attribute tag throw OrthancException(ErrorCode_NotImplemented); @@ -1684,6 +1552,9 @@ } case EVR_UL: // unsigned long +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: // other long (requires byte-swapping) +#endif { ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good(); break; @@ -1696,12 +1567,16 @@ } case EVR_FL: // float single-precision + case EVR_OF: // other float (requires byte swapping) { ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good(); break; } case EVR_FD: // float double-precision +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: // other double (requires byte-swapping) +#endif { ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good(); break; @@ -1751,6 +1626,7 @@ if (!ok) { + DicomTag tag(element.getTag().getGroup(), element.getTag().getElement()); throw OrthancException(ErrorCode_BadFileFormat, "While creating a DICOM instance, tag (" + tag.Format() + ") has out-of-range value: \"" + (*decoded) + "\""); @@ -1761,20 +1637,21 @@ DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, const Json::Value& value, bool decodeDataUriScheme, - Encoding dicomEncoding) + Encoding dicomEncoding, + const std::string& privateCreator) { - std::auto_ptr<DcmElement> element; + std::unique_ptr<DcmElement> element; switch (value.type()) { case Json::stringValue: - element.reset(CreateElementForTag(tag)); - FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding); + element.reset(CreateElementForTag(tag, privateCreator)); + FillElementWithString(*element, value.asString(), decodeDataUriScheme, dicomEncoding); break; case Json::nullValue: - element.reset(CreateElementForTag(tag)); - FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding); + element.reset(CreateElementForTag(tag, privateCreator)); + FillElementWithString(*element, "", decodeDataUriScheme, dicomEncoding); break; case Json::arrayValue: @@ -1790,7 +1667,7 @@ for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) { - std::auto_ptr<DcmItem> item(new DcmItem); + std::unique_ptr<DcmItem> item(new DcmItem); switch (value[i].type()) { @@ -1799,7 +1676,7 @@ Json::Value::Members members = value[i].getMemberNames(); for (Json::Value::ArrayIndex j = 0; j < members.size(); j++) { - item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding)); + item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding, privateCreator)); } break; } @@ -1908,9 +1785,10 @@ DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 bool generateIdentifiers, bool decodeDataUriScheme, - Encoding defaultEncoding) + Encoding defaultEncoding, + const std::string& privateCreator) { - std::auto_ptr<DcmDataset> result(new DcmDataset); + std::unique_ptr<DcmDataset> result(new DcmDataset); Encoding encoding = ExtractEncoding(json, defaultEncoding); SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding)); @@ -1946,7 +1824,7 @@ if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) { - std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); const DcmTagKey& tag = element->getTag(); result->findAndDeleteElement(tag); @@ -1998,10 +1876,18 @@ } is.setEos(); - std::auto_ptr<DcmFileFormat> result(new DcmFileFormat); + std::unique_ptr<DcmFileFormat> result(new DcmFileFormat); result->transferInit(); - if (!result->read(is).good()) + + /** + * New in Orthanc 1.6.0: The "size" is given as an argument to the + * "read()" method. This can avoid huge memory consumption if + * parsing an invalid DICOM file, which can notably been observed + * by executing the integration test "test_upload_compressed" on + * valgrind running Orthanc. + **/ + if (!result->read(is, EXS_Unknown, EGL_noChange, size).good()) { throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse an invalid DICOM file (size: " + @@ -2148,11 +2034,12 @@ void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, - DcmItem& dataset) + DcmItem& dataset, + const std::set<DicomTag>& ignoreTagLength) { ExtractDicomSummary(target, dataset, ORTHANC_MAXIMUM_TAG_LENGTH, - GetDefaultDicomEncoding()); + GetDefaultDicomEncoding(), ignoreTagLength); } @@ -2173,12 +2060,18 @@ { #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 LOG(INFO) << "Registering JPEG Lossless codecs in DCMTK"; - DJLSDecoderRegistration::registerCodecs(); + DJLSDecoderRegistration::registerCodecs(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJLSEncoderRegistration::registerCodecs(); +# endif #endif #if ORTHANC_ENABLE_DCMTK_JPEG == 1 LOG(INFO) << "Registering JPEG codecs in DCMTK"; DJDecoderRegistration::registerCodecs(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJEncoderRegistration::registerCodecs(); +# endif #endif } @@ -2188,11 +2081,17 @@ #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 // Unregister JPEG-LS codecs DJLSDecoderRegistration::cleanup(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJLSEncoderRegistration::cleanup(); +# endif #endif #if ORTHANC_ENABLE_DCMTK_JPEG == 1 // Unregister JPEG codecs DJDecoderRegistration::cleanup(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJEncoderRegistration::cleanup(); +# endif #endif } @@ -2268,13 +2167,6 @@ **/ if (evr == EVR_OB || // other byte - evr == EVR_OF || // other float -#if DCMTK_VERSION_NUMBER >= 361 - evr == EVR_OD || // other double -#endif -#if DCMTK_VERSION_NUMBER >= 362 - evr == EVR_OL || // other long -#endif evr == EVR_OW || // other word evr == EVR_UN) // unknown value representation { @@ -2464,6 +2356,9 @@ } case EVR_UL: // unsigned long +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: +#endif { DcmUnsignedLong& content = dynamic_cast<DcmUnsignedLong&>(element); @@ -2504,6 +2399,7 @@ } case EVR_FL: // float single-precision + case EVR_OF: { DcmFloatingPointSingle& content = dynamic_cast<DcmFloatingPointSingle&>(element); @@ -2524,6 +2420,9 @@ } case EVR_FD: // float double-precision +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: +#endif { DcmFloatingPointDouble& content = dynamic_cast<DcmFloatingPointDouble&>(element); @@ -2680,3 +2579,6 @@ ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); } } + + +#include "./FromDcmtkBridge_TransferSyntaxes.impl.h"
--- a/Core/DicomParsing/FromDcmtkBridge.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -84,7 +84,8 @@ static void ExtractDicomSummary(DicomMap& target, DcmItem& dataset, unsigned int maxStringLength, - Encoding defaultEncoding); + Encoding defaultEncoding, + const std::set<DicomTag>& ignoreTagLength); static void DatasetToJson(Json::Value& parent, DcmItem& item, @@ -191,7 +192,8 @@ const std::string& tagName, DicomValue* value) { - target.SetValue(ParseTag(tagName), value); + const DicomTag tag = ParseTag(tagName); + target.SetValueInternal(tag.GetGroup(), tag.GetElement(), value); } static void ToJson(Json::Value& result, @@ -207,10 +209,10 @@ static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); - static DcmElement* CreateElementForTag(const DicomTag& tag); + static DcmElement* CreateElementForTag(const DicomTag& tag, + const std::string& privateCreator); static void FillElementWithString(DcmElement& element, - const DicomTag& tag, const std::string& utf8alue, // Encoded using UTF-8 bool decodeDataUriScheme, Encoding dicomEncoding); @@ -218,7 +220,8 @@ static DcmElement* FromJson(const DicomTag& tag, const Json::Value& element, // Encoded using UTF-8 bool decodeDataUriScheme, - Encoding dicomEncoding); + Encoding dicomEncoding, + const std::string& privateCreator); static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset); @@ -228,7 +231,8 @@ static DcmDataset* FromJson(const Json::Value& json, // Encoded using UTF-8 bool generateIdentifiers, bool decodeDataUriScheme, - Encoding defaultEncoding); + Encoding defaultEncoding, + const std::string& privateCreator); static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer, size_t size); @@ -245,7 +249,15 @@ #endif static void ExtractDicomSummary(DicomMap& target, - DcmItem& dataset); + DcmItem& dataset, + const std::set<DicomTag>& ignoreTagLength); + + static void ExtractDicomSummary(DicomMap& target, + DcmItem& dataset) + { + std::set<DicomTag> none; + ExtractDicomSummary(target, dataset, none); + } static void ExtractDicomAsJson(Json::Value& target, DcmDataset& dataset, @@ -258,5 +270,11 @@ static void Apply(DcmItem& dataset, ITagVisitor& visitor, Encoding defaultEncoding); + + static bool LookupDcmtkTransferSyntax(E_TransferSyntax& target, + DicomTransferSyntax source); + + static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target, + E_TransferSyntax source); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/FromDcmtkBridge_TransferSyntaxes.impl.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,549 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/>. + **/ + +// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py" + +namespace Orthanc +{ + bool FromDcmtkBridge::LookupDcmtkTransferSyntax(E_TransferSyntax& target, + DicomTransferSyntax source) + { + switch (source) + { + case DicomTransferSyntax_LittleEndianImplicit: + target = EXS_LittleEndianImplicit; + return true; + + case DicomTransferSyntax_LittleEndianExplicit: + target = EXS_LittleEndianExplicit; + return true; + + case DicomTransferSyntax_DeflatedLittleEndianExplicit: + target = EXS_DeflatedLittleEndianExplicit; + return true; + + case DicomTransferSyntax_BigEndianExplicit: + target = EXS_BigEndianExplicit; + return true; + + case DicomTransferSyntax_JPEGProcess1: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess1TransferSyntax; +# else + target = EXS_JPEGProcess1; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess2_4: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess2_4TransferSyntax; +# else + target = EXS_JPEGProcess2_4; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess3_5: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess3_5TransferSyntax; +# else + target = EXS_JPEGProcess3_5; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess6_8: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess6_8TransferSyntax; +# else + target = EXS_JPEGProcess6_8; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess7_9: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess7_9TransferSyntax; +# else + target = EXS_JPEGProcess7_9; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess10_12: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess10_12TransferSyntax; +# else + target = EXS_JPEGProcess10_12; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess11_13: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess11_13TransferSyntax; +# else + target = EXS_JPEGProcess11_13; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess14: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess14TransferSyntax; +# else + target = EXS_JPEGProcess14; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess15: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess15TransferSyntax; +# else + target = EXS_JPEGProcess15; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess16_18: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess16_18TransferSyntax; +# else + target = EXS_JPEGProcess16_18; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess17_19: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess17_19TransferSyntax; +# else + target = EXS_JPEGProcess17_19; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess20_22: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess20_22TransferSyntax; +# else + target = EXS_JPEGProcess20_22; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess21_23: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess21_23TransferSyntax; +# else + target = EXS_JPEGProcess21_23; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess24_26: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess24_26TransferSyntax; +# else + target = EXS_JPEGProcess24_26; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess25_27: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess25_27TransferSyntax; +# else + target = EXS_JPEGProcess25_27; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess28: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess28TransferSyntax; +# else + target = EXS_JPEGProcess28; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess29: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess29TransferSyntax; +# else + target = EXS_JPEGProcess29; +# endif + return true; + + case DicomTransferSyntax_JPEGProcess14SV1: +# if DCMTK_VERSION_NUMBER <= 360 + target = EXS_JPEGProcess14SV1TransferSyntax; +# else + target = EXS_JPEGProcess14SV1; +# endif + return true; + + case DicomTransferSyntax_JPEGLSLossless: + target = EXS_JPEGLSLossless; + return true; + + case DicomTransferSyntax_JPEGLSLossy: + target = EXS_JPEGLSLossy; + return true; + + case DicomTransferSyntax_JPEG2000LosslessOnly: + target = EXS_JPEG2000LosslessOnly; + return true; + + case DicomTransferSyntax_JPEG2000: + target = EXS_JPEG2000; + return true; + + case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly: + target = EXS_JPEG2000MulticomponentLosslessOnly; + return true; + + case DicomTransferSyntax_JPEG2000Multicomponent: + target = EXS_JPEG2000Multicomponent; + return true; + + case DicomTransferSyntax_JPIPReferenced: + target = EXS_JPIPReferenced; + return true; + + case DicomTransferSyntax_JPIPReferencedDeflate: + target = EXS_JPIPReferencedDeflate; + return true; + + case DicomTransferSyntax_MPEG2MainProfileAtMainLevel: + target = EXS_MPEG2MainProfileAtMainLevel; + return true; + + case DicomTransferSyntax_MPEG2MainProfileAtHighLevel: + target = EXS_MPEG2MainProfileAtHighLevel; + return true; + +#if DCMTK_VERSION_NUMBER >= 361 + case DicomTransferSyntax_MPEG4HighProfileLevel4_1: + target = EXS_MPEG4HighProfileLevel4_1; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1: + target = EXS_MPEG4BDcompatibleHighProfileLevel4_1; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo: + target = EXS_MPEG4HighProfileLevel4_2_For2DVideo; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo: + target = EXS_MPEG4HighProfileLevel4_2_For3DVideo; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2: + target = EXS_MPEG4StereoHighProfileLevel4_2; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 362 + case DicomTransferSyntax_HEVCMainProfileLevel5_1: + target = EXS_HEVCMainProfileLevel5_1; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 362 + case DicomTransferSyntax_HEVCMain10ProfileLevel5_1: + target = EXS_HEVCMain10ProfileLevel5_1; + return true; +#endif + + case DicomTransferSyntax_RLELossless: + target = EXS_RLELossless; + return true; + + default: + return false; + } + } + + + bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target, + E_TransferSyntax source) + { + switch (source) + { + case EXS_LittleEndianImplicit: + target = DicomTransferSyntax_LittleEndianImplicit; + return true; + + case EXS_LittleEndianExplicit: + target = DicomTransferSyntax_LittleEndianExplicit; + return true; + + case EXS_DeflatedLittleEndianExplicit: + target = DicomTransferSyntax_DeflatedLittleEndianExplicit; + return true; + + case EXS_BigEndianExplicit: + target = DicomTransferSyntax_BigEndianExplicit; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess1TransferSyntax: +# else + case EXS_JPEGProcess1: +# endif + target = DicomTransferSyntax_JPEGProcess1; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess2_4TransferSyntax: +# else + case EXS_JPEGProcess2_4: +# endif + target = DicomTransferSyntax_JPEGProcess2_4; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess3_5TransferSyntax: +# else + case EXS_JPEGProcess3_5: +# endif + target = DicomTransferSyntax_JPEGProcess3_5; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess6_8TransferSyntax: +# else + case EXS_JPEGProcess6_8: +# endif + target = DicomTransferSyntax_JPEGProcess6_8; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess7_9TransferSyntax: +# else + case EXS_JPEGProcess7_9: +# endif + target = DicomTransferSyntax_JPEGProcess7_9; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess10_12TransferSyntax: +# else + case EXS_JPEGProcess10_12: +# endif + target = DicomTransferSyntax_JPEGProcess10_12; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess11_13TransferSyntax: +# else + case EXS_JPEGProcess11_13: +# endif + target = DicomTransferSyntax_JPEGProcess11_13; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess14TransferSyntax: +# else + case EXS_JPEGProcess14: +# endif + target = DicomTransferSyntax_JPEGProcess14; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess15TransferSyntax: +# else + case EXS_JPEGProcess15: +# endif + target = DicomTransferSyntax_JPEGProcess15; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess16_18TransferSyntax: +# else + case EXS_JPEGProcess16_18: +# endif + target = DicomTransferSyntax_JPEGProcess16_18; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess17_19TransferSyntax: +# else + case EXS_JPEGProcess17_19: +# endif + target = DicomTransferSyntax_JPEGProcess17_19; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess20_22TransferSyntax: +# else + case EXS_JPEGProcess20_22: +# endif + target = DicomTransferSyntax_JPEGProcess20_22; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess21_23TransferSyntax: +# else + case EXS_JPEGProcess21_23: +# endif + target = DicomTransferSyntax_JPEGProcess21_23; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess24_26TransferSyntax: +# else + case EXS_JPEGProcess24_26: +# endif + target = DicomTransferSyntax_JPEGProcess24_26; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess25_27TransferSyntax: +# else + case EXS_JPEGProcess25_27: +# endif + target = DicomTransferSyntax_JPEGProcess25_27; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess28TransferSyntax: +# else + case EXS_JPEGProcess28: +# endif + target = DicomTransferSyntax_JPEGProcess28; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess29TransferSyntax: +# else + case EXS_JPEGProcess29: +# endif + target = DicomTransferSyntax_JPEGProcess29; + return true; + +# if DCMTK_VERSION_NUMBER <= 360 + case EXS_JPEGProcess14SV1TransferSyntax: +# else + case EXS_JPEGProcess14SV1: +# endif + target = DicomTransferSyntax_JPEGProcess14SV1; + return true; + + case EXS_JPEGLSLossless: + target = DicomTransferSyntax_JPEGLSLossless; + return true; + + case EXS_JPEGLSLossy: + target = DicomTransferSyntax_JPEGLSLossy; + return true; + + case EXS_JPEG2000LosslessOnly: + target = DicomTransferSyntax_JPEG2000LosslessOnly; + return true; + + case EXS_JPEG2000: + target = DicomTransferSyntax_JPEG2000; + return true; + + case EXS_JPEG2000MulticomponentLosslessOnly: + target = DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly; + return true; + + case EXS_JPEG2000Multicomponent: + target = DicomTransferSyntax_JPEG2000Multicomponent; + return true; + + case EXS_JPIPReferenced: + target = DicomTransferSyntax_JPIPReferenced; + return true; + + case EXS_JPIPReferencedDeflate: + target = DicomTransferSyntax_JPIPReferencedDeflate; + return true; + + case EXS_MPEG2MainProfileAtMainLevel: + target = DicomTransferSyntax_MPEG2MainProfileAtMainLevel; + return true; + + case EXS_MPEG2MainProfileAtHighLevel: + target = DicomTransferSyntax_MPEG2MainProfileAtHighLevel; + return true; + +#if DCMTK_VERSION_NUMBER >= 361 + case EXS_MPEG4HighProfileLevel4_1: + target = DicomTransferSyntax_MPEG4HighProfileLevel4_1; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case EXS_MPEG4BDcompatibleHighProfileLevel4_1: + target = DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case EXS_MPEG4HighProfileLevel4_2_For2DVideo: + target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case EXS_MPEG4HighProfileLevel4_2_For3DVideo: + target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case EXS_MPEG4StereoHighProfileLevel4_2: + target = DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 362 + case EXS_HEVCMainProfileLevel5_1: + target = DicomTransferSyntax_HEVCMainProfileLevel5_1; + return true; +#endif + +#if DCMTK_VERSION_NUMBER >= 362 + case EXS_HEVCMain10ProfileLevel5_1: + target = DicomTransferSyntax_HEVCMain10ProfileLevel5_1; + return true; +#endif + + case EXS_RLELossless: + target = DicomTransferSyntax_RLELossless; + return true; + + default: + return false; + } + } +}
--- a/Core/DicomParsing/ITagVisitor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/ITagVisitor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -71,7 +71,7 @@ ValueRepresentation vr, const std::vector<int64_t>& values) = 0; - // FL, FD + // FL, FD, OD, OF virtual void VisitDoubles(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, @@ -84,7 +84,7 @@ const DicomTag& tag, const std::vector<DicomTag>& values) = 0; - // OB, OD, OF, OL, OW, UN + // OB, OL, OW, UN virtual void VisitBinary(const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag,
--- a/Core/DicomParsing/Internals/DicomFrameIndex.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/Internals/DicomFrameIndex.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -68,7 +68,10 @@ uint32_t length = item->getLength(); if (length == 0) { - table.clear(); + // Degenerate case: Empty offset table means only one frame + // that overlaps all the fragments + table.resize(1); + table[0] = 0; return; } @@ -146,7 +149,6 @@ throw OrthancException(ErrorCode_BadFileFormat); } - // Loop over the fragments (ignoring the offset table). This is // an alternative, faster implementation to DCMTK's // "DcmCodec::determineStartFragment()". @@ -318,46 +320,10 @@ }; - - bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom) + unsigned int DicomFrameIndex::GetFramesCount(DcmDataset& dicom) { - // Retrieve the transfer syntax from the DICOM header - const char* value = NULL; - if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() || - value == NULL) - { - return false; - } - - const std::string transferSyntax(value); - - // Video standards supported in DICOM 2016a - // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html - if (transferSyntax == "1.2.840.10008.1.2.4.100" || // MPEG2 MP@ML option of ISO/IEC MPEG2 - transferSyntax == "1.2.840.10008.1.2.4.101" || // MPEG2 MP@HL option of ISO/IEC MPEG2 - transferSyntax == "1.2.840.10008.1.2.4.102" || // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.103" || // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.104" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.105" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.106") // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264 - { - return true; - } - - return false; - } - - - unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom) - { - // Assume 1 frame for video transfer syntaxes - if (IsVideo(dicom)) - { - return 1; - } - const char* tmp = NULL; - if (!dicom.getDataset()->findAndGetString(DCM_NumberOfFrames, tmp).good() || + if (!dicom.findAndGetString(DCM_NumberOfFrames, tmp).good() || tmp == NULL) { return 1; @@ -378,12 +344,12 @@ } else { - return count; + return static_cast<unsigned int>(count); } } - DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom) + DicomFrameIndex::DicomFrameIndex(DcmDataset& dicom) { countFrames_ = GetFramesCount(dicom); if (countFrames_ == 0) @@ -392,10 +358,8 @@ return; } - DcmDataset& dataset = *dicom.getDataset(); - // Test whether this image is composed of a sequence of fragments - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); + DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dicom); if (pixelSequence != NULL) { index_.reset(new FragmentIndex(pixelSequence, countFrames_)); @@ -404,18 +368,18 @@ // Extract information about the image structure DicomMap tags; - FromDcmtkBridge::ExtractDicomSummary(tags, dataset); + FromDcmtkBridge::ExtractDicomSummary(tags, dicom); DicomImageInformation information(tags); // Access to the raw pixel data - if (DicomImageDecoder::IsPsmctRle1(dataset)) + if (DicomImageDecoder::IsPsmctRle1(dicom)) { - index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize())); + index_.reset(new PsmctRle1Index(dicom, countFrames_, information.GetFrameSize())); } else { - index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize())); + index_.reset(new UncompressedIndex(dicom, countFrames_, information.GetFrameSize())); } }
--- a/Core/DicomParsing/Internals/DicomFrameIndex.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/Internals/DicomFrameIndex.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "../../Compatibility.h" #include "../../Enumerations.h" #include <dcmtk/dcmdata/dcdatset.h> @@ -62,11 +63,11 @@ class UncompressedIndex; class PsmctRle1Index; - std::auto_ptr<IIndex> index_; - unsigned int countFrames_; + std::unique_ptr<IIndex> index_; + unsigned int countFrames_; public: - DicomFrameIndex(DcmFileFormat& dicom); + DicomFrameIndex(DcmDataset& dicom); unsigned int GetFramesCount() const { @@ -76,8 +77,6 @@ void GetRawFrame(std::string& frame, unsigned int index) const; - static bool IsVideo(DcmFileFormat& dicom); - - static unsigned int GetFramesCount(DcmFileFormat& dicom); + static unsigned int GetFramesCount(DcmDataset& dicom); }; }
--- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,8 @@ #include "../../PrecompiledHeaders.h" #include "DicomImageDecoder.h" +#include "../ParsedDicomFile.h" + /*========================================================================= @@ -84,7 +86,6 @@ #include "../../DicomFormat/DicomIntegerPixelAccessor.h" #include "../ToDcmtkBridge.h" #include "../FromDcmtkBridge.h" -#include "../ParsedDicomFile.h" #if ORTHANC_ENABLE_PNG == 1 # include "../../Images/PngWriter.h" @@ -98,7 +99,6 @@ #include <boost/lexical_cast.hpp> #include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmdata/dcfilefo.h> #include <dcmtk/dcmdata/dcrleccd.h> #include <dcmtk/dcmdata/dcrlecp.h> #include <dcmtk/dcmdata/dcrlerp.h> @@ -249,7 +249,7 @@ { private: std::string psmct_; - std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_; + std::unique_ptr<DicomIntegerPixelAccessor> slowAccessor_; public: void Setup(DcmDataset& dataset, @@ -388,7 +388,7 @@ } - static ImageAccessor* DecodeLookupTable(std::auto_ptr<ImageAccessor>& target, + static ImageAccessor* DecodeLookupTable(std::unique_ptr<ImageAccessor>& target, const DicomImageInformation& info, DcmDataset& dataset, const uint8_t* pixelData, @@ -508,7 +508,7 @@ * Create the target image. **/ - std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false)); + std::unique_ptr<ImageAccessor> target(CreateImage(dataset, false)); ImageSource source; source.Setup(dataset, frame); @@ -616,7 +616,7 @@ FromDcmtkBridge::ExtractDicomSummary(m, dataset); DicomImageInformation info(m); - std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true)); + std::unique_ptr<ImageAccessor> target(CreateImage(dataset, true)); Uint32 startFragment = 0; // Default OFString decompressedColorModel; // Out @@ -662,7 +662,20 @@ ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, unsigned int frame) { - DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + if (dicom.GetDcmtkObject().getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + return Decode(*dicom.GetDcmtkObject().getDataset(), frame); + } + } + + + ImageAccessor* DicomImageDecoder::Decode(DcmDataset& dataset, + unsigned int frame) + { E_TransferSyntax syntax = dataset.getOriginalXfer(); /** @@ -692,7 +705,7 @@ DJLSRepresentationParameter representationParameter(2, OFTrue); DJLSCodecParameter parameters; - std::auto_ptr<DJLSDecoderBase> decoder; + std::unique_ptr<DJLSDecoderBase> decoder; switch (syntax) { @@ -734,7 +747,7 @@ EUC_default, // Mode for UID creation, unused for decompression EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation DJ_RPLossy representationParameter; - std::auto_ptr<DJCodecDecoder> decoder; + std::unique_ptr<DJCodecDecoder> decoder; switch (syntax) { @@ -799,7 +812,7 @@ { LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian"; - std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); + std::unique_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); if (converted->canWriteXfer(EXS_LittleEndianExplicit)) @@ -820,7 +833,7 @@ } - bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, + bool DicomImageDecoder::TruncateDecodedImage(std::unique_ptr<ImageAccessor>& image, PixelFormat format, bool allowColorConversion) { @@ -840,17 +853,22 @@ if (image->GetFormat() != format) { // A conversion is required - std::auto_ptr<ImageAccessor> target + std::unique_ptr<ImageAccessor> target (new Image(format, image->GetWidth(), image->GetHeight(), false)); ImageProcessing::Convert(*target, *image); - image = target; + +#if __cplusplus < 201103L + image.reset(target.release()); +#else + image = std::move(target); +#endif } return true; } - bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image) + bool DicomImageDecoder::PreviewDecodedImage(std::unique_ptr<ImageAccessor>& image) { switch (image->GetFormat()) { @@ -862,10 +880,16 @@ case PixelFormat_RGB48: { - std::auto_ptr<ImageAccessor> target + std::unique_ptr<ImageAccessor> target (new Image(PixelFormat_RGB24, image->GetWidth(), image->GetHeight(), false)); ImageProcessing::Convert(*target, *image); - image = target; + +#if __cplusplus < 201103L + image.reset(target.release()); +#else + image = std::move(target); +#endif + return true; } @@ -891,10 +915,15 @@ // If the source image is not grayscale 8bpp, convert it if (image->GetFormat() != PixelFormat_Grayscale8) { - std::auto_ptr<ImageAccessor> target + std::unique_ptr<ImageAccessor> target (new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); ImageProcessing::Convert(*target, *image); - image = target; + +#if __cplusplus < 201103L + image.reset(target.release()); +#else + image = std::move(target); +#endif } return true; @@ -906,7 +935,7 @@ } - void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, + void DicomImageDecoder::ApplyExtractionMode(std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert) { @@ -956,7 +985,7 @@ void DicomImageDecoder::ExtractPamImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, + std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert) { @@ -968,7 +997,7 @@ #if ORTHANC_ENABLE_PNG == 1 void DicomImageDecoder::ExtractPngImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, + std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert) { @@ -982,7 +1011,7 @@ #if ORTHANC_ENABLE_JPEG == 1 void DicomImageDecoder::ExtractJpegImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, + std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert, uint8_t quality)
--- a/Core/DicomParsing/Internals/DicomImageDecoder.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,7 +33,8 @@ #pragma once -#include "../ParsedDicomFile.h" +#include "../../Compatibility.h" +#include "../../Images/ImageAccessor.h" #include <memory> @@ -61,6 +62,8 @@ namespace Orthanc { + class ParsedDicomFile; + class DicomImageDecoder : public boost::noncopyable { private: @@ -82,13 +85,13 @@ DcmDataset& dataset, unsigned int frame); - static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, + static bool TruncateDecodedImage(std::unique_ptr<ImageAccessor>& image, PixelFormat format, bool allowColorConversion); - static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image); + static bool PreviewDecodedImage(std::unique_ptr<ImageAccessor>& image); - static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, + static void ApplyExtractionMode(std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert); @@ -101,21 +104,24 @@ static ImageAccessor *Decode(ParsedDicomFile& dicom, unsigned int frame); + static ImageAccessor *Decode(DcmDataset& dataset, + unsigned int frame); + static void ExtractPamImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, + std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert); #if ORTHANC_ENABLE_PNG == 1 static void ExtractPngImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, + std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert); #endif #if ORTHANC_ENABLE_JPEG == 1 static void ExtractJpegImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, + std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert, uint8_t quality);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ParsedDicomDir.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,195 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "../PrecompiledHeaders.h" +#include "ParsedDicomDir.h" + +#include "../Compatibility.h" +#include "../OrthancException.h" +#include "ParsedDicomFile.h" +#include "FromDcmtkBridge.h" + +#include <dcmtk/dcmdata/dcdeftag.h> + + +namespace Orthanc +{ + void ParsedDicomDir::Clear() + { + for (size_t i = 0; i < content_.size(); i++) + { + assert(content_[i] != NULL); + delete content_[i]; + } + } + + + bool ParsedDicomDir::LookupIndexOfOffset(size_t& target, + unsigned int offset) const + { + if (offset == 0) + { + return false; + } + + OffsetToIndex::const_iterator found = offsetToIndex_.find(offset); + if (found == offsetToIndex_.end()) + { + // Error in the algorithm that computes the offsets + throw OrthancException(ErrorCode_InternalError); + } + else + { + target = found->second; + return true; + } + } + + + ParsedDicomDir::ParsedDicomDir(const std::string content) + { + ParsedDicomFile dicom(content); + + DcmSequenceOfItems* sequence = NULL; + if (dicom.GetDcmtkObject().getDataset() == NULL || + !dicom.GetDcmtkObject().getDataset()->findAndGetSequence(DCM_DirectoryRecordSequence, sequence).good() || + sequence == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat, "Not a DICOMDIR"); + } + + content_.resize(sequence->card()); + nextOffsets_.resize(content_.size()); + lowerOffsets_.resize(content_.size()); + + // Manually reconstruct the list of all the available offsets of + // "DcmItem", as "fStartPosition" is a protected member in DCMTK + // API + std::set<uint32_t> availableOffsets; + availableOffsets.insert(0); + + + for (unsigned long i = 0; i < sequence->card(); i++) + { + DcmItem* item = sequence->getItem(i); + if (item == NULL) + { + Clear(); + throw OrthancException(ErrorCode_InternalError); + } + + Uint32 next, lower; + if (!item->findAndGetUint32(DCM_OffsetOfTheNextDirectoryRecord, next).good() || + !item->findAndGetUint32(DCM_OffsetOfReferencedLowerLevelDirectoryEntity, lower).good()) + { + item->writeXML(std::cout); + throw OrthancException(ErrorCode_BadFileFormat, + "Missing offsets in DICOMDIR"); + } + + nextOffsets_[i] = next; + lowerOffsets_[i] = lower; + + std::unique_ptr<DicomMap> entry(new DicomMap); + FromDcmtkBridge::ExtractDicomSummary(*entry, *item); + + if (next != 0) + { + availableOffsets.insert(next); + } + + if (lower != 0) + { + availableOffsets.insert(lower); + } + + content_[i] = entry.release(); + } + + if (content_.size() != availableOffsets.size()) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Inconsistent offsets in DICOMDIR"); + } + + unsigned int index = 0; + for (std::set<uint32_t>::const_iterator it = availableOffsets.begin(); + it != availableOffsets.end(); ++it) + { + offsetToIndex_[*it] = index; + index ++; + } + } + + + const DicomMap& ParsedDicomDir::GetItem(size_t i) const + { + if (i >= content_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + assert(content_[i] != NULL); + return *content_[i]; + } + } + + + bool ParsedDicomDir::LookupNext(size_t& target, + size_t index) const + { + if (index >= nextOffsets_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return LookupIndexOfOffset(target, nextOffsets_[index]); + } + } + + + bool ParsedDicomDir::LookupLower(size_t& target, + size_t index) const + { + if (index >= lowerOffsets_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return LookupIndexOfOffset(target, lowerOffsets_[index]); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ParsedDicomDir.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,80 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 + +#if ORTHANC_ENABLE_DCMTK != 1 +# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 to use this file +#endif + +#include "../DicomFormat/DicomMap.h" + +namespace Orthanc +{ + class ParsedDicomDir : public boost::noncopyable + { + private: + typedef std::map<uint32_t, size_t> OffsetToIndex; + + std::vector<DicomMap*> content_; + std::vector<size_t> nextOffsets_; + std::vector<size_t> lowerOffsets_; + OffsetToIndex offsetToIndex_; + + void Clear(); + + bool LookupIndexOfOffset(size_t& target, + unsigned int offset) const; + + public: + ParsedDicomDir(const std::string content); + + ~ParsedDicomDir() + { + Clear(); + } + + size_t GetSize() const + { + return content_.size(); + } + + const DicomMap& GetItem(size_t i) const; + + bool LookupNext(size_t& target, + size_t index) const; + + bool LookupLower(size_t& target, + size_t index) const; + }; +}
--- a/Core/DicomParsing/ParsedDicomFile.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/ParsedDicomFile.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -156,8 +156,8 @@ { struct ParsedDicomFile::PImpl { - std::auto_ptr<DcmFileFormat> file_; - std::auto_ptr<DicomFrameIndex> frameIndex_; + std::unique_ptr<DcmFileFormat> file_; + std::unique_ptr<DicomFrameIndex> frameIndex_; }; @@ -619,7 +619,8 @@ void ParsedDicomFile::Insert(const DicomTag& tag, const Json::Value& value, - bool decodeDataUriScheme) + bool decodeDataUriScheme, + const std::string& privateCreator) { if (tag.GetElement() == 0x0000) { @@ -648,11 +649,38 @@ bool hasCodeExtensions; Encoding encoding = DetectEncoding(hasCodeExtensions); - std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); InsertInternal(*pimpl_->file_->getDataset(), element.release()); } + void ParsedDicomFile::ReplacePlainString(const DicomTag& tag, + const std::string& utf8Value) + { + if (tag.IsPrivate()) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot apply this function to private tags: " + tag.Format()); + } + else + { + Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent, + "" /* not a private tag, so no private creator */); + } + } + + + void ParsedDicomFile::SetIfAbsent(const DicomTag& tag, + const std::string& utf8Value) + { + std::string currentValue; + if (!GetTagValue(currentValue, tag)) + { + ReplacePlainString(tag, utf8Value); + } + } + + static bool CanReplaceProceed(DcmDataset& dicom, const DcmTagKey& tag, DicomReplaceMode mode) @@ -742,7 +770,8 @@ void ParsedDicomFile::Replace(const DicomTag& tag, const std::string& utf8Value, bool decodeDataUriScheme, - DicomReplaceMode mode) + DicomReplaceMode mode, + const std::string& privateCreator) { if (tag.GetElement() == 0x0000) { @@ -769,13 +798,13 @@ } } - std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); + std::unique_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator)); if (!utf8Value.empty()) { bool hasCodeExtensions; Encoding encoding = DetectEncoding(hasCodeExtensions); - FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, encoding); + FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding); } InsertInternal(dicom, element.release()); @@ -787,7 +816,8 @@ void ParsedDicomFile::Replace(const DicomTag& tag, const Json::Value& value, bool decodeDataUriScheme, - DicomReplaceMode mode) + DicomReplaceMode mode, + const std::string& privateCreator) { if (tag.GetElement() == 0x0000) { @@ -817,7 +847,7 @@ bool hasCodeExtensions; Encoding encoding = DetectEncoding(hasCodeExtensions); - InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); if (tag == DICOM_TAG_SOP_CLASS_UID || tag == DICOM_TAG_SOP_INSTANCE_UID) @@ -891,9 +921,9 @@ Encoding encoding = DetectEncoding(hasCodeExtensions); std::set<DicomTag> tmp; - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement - (*element, DicomToJsonFlags_Default, - 0, encoding, hasCodeExtensions, tmp)); + std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_Default, + 0, encoding, hasCodeExtensions, tmp)); if (v.get() == NULL || v->IsNull()) @@ -1007,8 +1037,8 @@ } } - for (DicomMap::Map::const_iterator - it = source.map_.begin(); it != source.map_.end(); ++it) + for (DicomMap::Content::const_iterator + it = source.content_.begin(); it != source.content_.end(); ++it) { if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET && !it->second->IsNull()) @@ -1279,7 +1309,7 @@ DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), DICOM_TAG_PIXEL_DATA.GetElement()); - std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key)); + std::unique_ptr<DcmPixelData> pixels(new DcmPixelData(key)); unsigned int pitch = accessor.GetWidth() * bytesPerPixel; Uint8* target = NULL; @@ -1414,7 +1444,7 @@ ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), MIME_PDF); //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); - std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); + std::unique_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); size_t s = pdf.size(); if (s & 1) @@ -1483,12 +1513,13 @@ ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json, - DicomFromJsonFlags flags) + DicomFromJsonFlags flags, + const std::string& privateCreator) { const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false; const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false; - std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers)); + std::unique_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers)); result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding())); const Json::Value::Members tags = json.getMemberNames(); @@ -1512,7 +1543,7 @@ } else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) { - result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent); + result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent, privateCreator); } } @@ -1526,7 +1557,9 @@ { if (pimpl_->frameIndex_.get() == NULL) { - pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_)); + assert(pimpl_->file_ != NULL && + pimpl_->file_->getDataset() != NULL); + pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_->getDataset())); } pimpl_->frameIndex_->GetRawFrame(target, frameId); @@ -1558,7 +1591,9 @@ unsigned int ParsedDicomFile::GetFramesCount() const { - return DicomFrameIndex::GetFramesCount(*pimpl_->file_); + assert(pimpl_->file_ != NULL && + pimpl_->file_->getDataset() != NULL); + return DicomFrameIndex::GetFramesCount(*pimpl_->file_->getDataset()); } @@ -1581,6 +1616,13 @@ } + void ParsedDicomFile::ExtractDicomSummary(DicomMap& target, + const std::set<DicomTag>& ignoreTagLength) const + { + FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset(), ignoreTagLength); + } + + bool ParsedDicomFile::LookupTransferSyntax(std::string& result) { return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_);
--- a/Core/DicomParsing/ParsedDicomFile.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/ParsedDicomFile.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -53,6 +53,14 @@ # error The macro ORTHANC_SANDBOXED must be defined #endif +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK != 1 +# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 to use this file +#endif + #include "ITagVisitor.h" #include "../DicomFormat/DicomInstanceHasher.h" #include "../Images/ImageAccessor.h" @@ -130,32 +138,27 @@ void Replace(const DicomTag& tag, const std::string& utf8Value, bool decodeDataUriScheme, - DicomReplaceMode mode); + DicomReplaceMode mode, + const std::string& privateCreator /* used only for private tags */); void Replace(const DicomTag& tag, const Json::Value& value, // Assumed to be encoded with UTF-8 bool decodeDataUriScheme, - DicomReplaceMode mode); + DicomReplaceMode mode, + const std::string& privateCreator /* used only for private tags */); void Insert(const DicomTag& tag, const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeDataUriScheme); + bool decodeDataUriScheme, + const std::string& privateCreator /* used only for private tags */); + // Cannot be applied to private tags void ReplacePlainString(const DicomTag& tag, - const std::string& utf8Value) - { - Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent); - } + const std::string& utf8Value); + // Cannot be applied to private tags void SetIfAbsent(const DicomTag& tag, - const std::string& utf8Value) - { - std::string currentValue; - if (!GetTagValue(currentValue, tag)) - { - ReplacePlainString(tag, utf8Value); - } - } + const std::string& utf8Value); void RemovePrivateTags() { @@ -226,12 +229,16 @@ unsigned int GetFramesCount() const; static ParsedDicomFile* CreateFromJson(const Json::Value& value, - DicomFromJsonFlags flags); + DicomFromJsonFlags flags, + const std::string& privateCreator); void ChangeEncoding(Encoding target); void ExtractDicomSummary(DicomMap& target) const; + void ExtractDicomSummary(DicomMap& target, + const std::set<DicomTag>& ignoreTagLength) const; + bool LookupTransferSyntax(std::string& result); bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const;
--- a/Core/DicomParsing/ToDcmtkBridge.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/ToDcmtkBridge.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/ToDcmtkBridge.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/DicomParsing/ToDcmtkBridge.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Endianness.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Endianness.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/EnumerationDictionary.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/EnumerationDictionary.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Enumerations.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Enumerations.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -366,6 +366,9 @@ case ErrorCode_AlreadyExistingTag: return "Cannot override the value of a tag that already exists"; + case ErrorCode_NoStorageCommitmentHandler: + return "No request handler factory for DICOM N-ACTION SCP (storage commitment)"; + case ErrorCode_UnsupportedMediaType: return "Unsupported media type"; @@ -824,12 +827,6 @@ case ModalityManufacturer_StoreScp: return "StoreScp"; - case ModalityManufacturer_ClearCanvas: - return "ClearCanvas"; - - case ModalityManufacturer_Dcm4Chee: - return "Dcm4Chee"; - case ModalityManufacturer_Vitrea: return "Vitrea"; @@ -866,6 +863,14 @@ return "Store"; break; + case DicomRequestType_NAction: + return "N-ACTION"; + break; + + case DicomRequestType_NEventReport: + return "N-EVENT-REPORT"; + break; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -894,6 +899,9 @@ case TransferSyntax_Mpeg2: return "MPEG2"; + case TransferSyntax_Mpeg4: + return "MPEG4"; + case TransferSyntax_Rle: return "RLE"; @@ -1160,6 +1168,41 @@ } + const char* EnumerationToString(StorageCommitmentFailureReason reason) + { + switch (reason) + { + case StorageCommitmentFailureReason_Success: + return "Success"; + + case StorageCommitmentFailureReason_ProcessingFailure: + return "A general failure in processing the operation was encountered"; + + case StorageCommitmentFailureReason_NoSuchObjectInstance: + return "One or more of the elements in the Referenced SOP " + "Instance Sequence was not available"; + + case StorageCommitmentFailureReason_ResourceLimitation: + return "The SCP does not currently have enough resources to " + "store the requested SOP Instance(s)"; + + case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: + return "Storage Commitment has been requested for a SOP Instance " + "with a SOP Class that is not supported by the SCP"; + + case StorageCommitmentFailureReason_ClassInstanceConflict: + return "The SOP Class of an element in the Referenced SOP Instance Sequence " + "did not correspond to the SOP class registered for this SOP Instance at the SCP"; + + case StorageCommitmentFailureReason_DuplicateTransactionUID: + return "The Transaction UID of the Storage Commitment Request is already in use"; + + default: + return "Unknown failure reason"; + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); @@ -1560,18 +1603,10 @@ { return ModalityManufacturer_GenericNoUniversalWildcard; } - else if (manufacturer == "ClearCanvas") - { - return ModalityManufacturer_ClearCanvas; - } else if (manufacturer == "StoreScp") { return ModalityManufacturer_StoreScp; } - else if (manufacturer == "Dcm4Chee") - { - return ModalityManufacturer_Dcm4Chee; - } else if (manufacturer == "Vitrea") { return ModalityManufacturer_Vitrea; @@ -1587,7 +1622,10 @@ obsolete = true; } else if (manufacturer == "EFilm2" || - manufacturer == "MedInria") + manufacturer == "MedInria" || + manufacturer == "ClearCanvas" || + manufacturer == "Dcm4Chee" + ) { result = ModalityManufacturer_Generic; obsolete = true; @@ -1600,8 +1638,8 @@ if (obsolete) { - LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since " - << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc " + LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is now obsolete. " + << "To guarantee compatibility with future Orthanc " << "releases, you should replace it by \"" << EnumerationToString(result) << "\" in your configuration file."; @@ -2252,5 +2290,7 @@ LOG(INFO) << "Default encoding for DICOM was changed to: " << name; } - } + + +#include "./Enumerations_TransferSyntaxes.impl.h"
--- a/Core/Enumerations.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Enumerations.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -239,10 +239,59 @@ ErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, ErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, ErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */, + ErrorCode_NoStorageCommitmentHandler = 2043 /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */, ErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */, ErrorCode_START_PLUGINS = 1000000 }; + // This enumeration is autogenerated by the script + // "Resources/GenerateTransferSyntaxes.py" + enum DicomTransferSyntax + { + DicomTransferSyntax_LittleEndianImplicit /*!< Implicit VR Little Endian */, + DicomTransferSyntax_LittleEndianExplicit /*!< Explicit VR Little Endian */, + DicomTransferSyntax_DeflatedLittleEndianExplicit /*!< Deflated Explicit VR Little Endian */, + DicomTransferSyntax_BigEndianExplicit /*!< Explicit VR Big Endian */, + DicomTransferSyntax_JPEGProcess1 /*!< JPEG Baseline (process 1, lossy) */, + DicomTransferSyntax_JPEGProcess2_4 /*!< JPEG Extended Sequential (processes 2 & 4) */, + DicomTransferSyntax_JPEGProcess3_5 /*!< JPEG Extended Sequential (lossy, 8/12 bit), arithmetic coding */, + DicomTransferSyntax_JPEGProcess6_8 /*!< JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit) */, + DicomTransferSyntax_JPEGProcess7_9 /*!< JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit), arithmetic coding */, + DicomTransferSyntax_JPEGProcess10_12 /*!< JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit) */, + DicomTransferSyntax_JPEGProcess11_13 /*!< JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit), arithmetic coding */, + DicomTransferSyntax_JPEGProcess14 /*!< JPEG Lossless, Nonhierarchical with any selection value (process 14) */, + DicomTransferSyntax_JPEGProcess15 /*!< JPEG Lossless with any selection value, arithmetic coding */, + DicomTransferSyntax_JPEGProcess16_18 /*!< JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit) */, + DicomTransferSyntax_JPEGProcess17_19 /*!< JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit), arithmetic coding */, + DicomTransferSyntax_JPEGProcess20_22 /*!< JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit) */, + DicomTransferSyntax_JPEGProcess21_23 /*!< JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit), arithmetic coding */, + DicomTransferSyntax_JPEGProcess24_26 /*!< JPEG Full Progression, Hierarchical (lossy, 8/12 bit) */, + DicomTransferSyntax_JPEGProcess25_27 /*!< JPEG Full Progression, Hierarchical (lossy, 8/12 bit), arithmetic coding */, + DicomTransferSyntax_JPEGProcess28 /*!< JPEG Lossless, Hierarchical */, + DicomTransferSyntax_JPEGProcess29 /*!< JPEG Lossless, Hierarchical, arithmetic coding */, + DicomTransferSyntax_JPEGProcess14SV1 /*!< JPEG Lossless, Nonhierarchical, First-Order Prediction (Processes 14 [Selection Value 1]) */, + DicomTransferSyntax_JPEGLSLossless /*!< JPEG-LS (lossless) */, + DicomTransferSyntax_JPEGLSLossy /*!< JPEG-LS (lossy or near-lossless) */, + DicomTransferSyntax_JPEG2000LosslessOnly /*!< JPEG 2000 (lossless) */, + DicomTransferSyntax_JPEG2000 /*!< JPEG 2000 (lossless or lossy) */, + DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly /*!< JPEG 2000 part 2 multicomponent extensions (lossless) */, + DicomTransferSyntax_JPEG2000Multicomponent /*!< JPEG 2000 part 2 multicomponent extensions (lossless or lossy) */, + DicomTransferSyntax_JPIPReferenced /*!< JPIP Referenced */, + DicomTransferSyntax_JPIPReferencedDeflate /*!< JPIP Referenced Deflate */, + DicomTransferSyntax_MPEG2MainProfileAtMainLevel /*!< MPEG2 Main Profile at Main Level */, + DicomTransferSyntax_MPEG2MainProfileAtHighLevel /*!< MPEG2 Main Profile at High Level */, + DicomTransferSyntax_MPEG4HighProfileLevel4_1 /*!< MPEG4 High Profile / Level 4.1 */, + DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1 /*!< MPEG4 BD-compatible High Profile / Level 4.1 */, + DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo /*!< MPEG4 High Profile / Level 4.2 For 2D Video */, + DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo /*!< MPEG4 High Profile / Level 4.2 For 3D Video */, + DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2 /*!< 1.2.840.10008.1.2.4.106 */, + DicomTransferSyntax_HEVCMainProfileLevel5_1 /*!< HEVC/H.265 Main Profile / Level 5.1 */, + DicomTransferSyntax_HEVCMain10ProfileLevel5_1 /*!< HEVC/H.265 Main 10 Profile / Level 5.1 */, + DicomTransferSyntax_RLELossless /*!< RLE - Run Length Encoding (lossless) */, + DicomTransferSyntax_RFC2557MimeEncapsulation /*!< RFC 2557 MIME Encapsulation */, + DicomTransferSyntax_XML /*!< XML Encoding */ + }; + enum LogLevel { LogLevel_Error, @@ -313,7 +362,7 @@ /** * {summary}{Graylevel, unsigned 64bpp image.} - * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.} + * {description}{The image is graylevel. Each pixel is unsigned and stored in 8 bytes.} **/ PixelFormat_Grayscale64 = 10 }; @@ -612,8 +661,6 @@ ModalityManufacturer_GenericNoWildcardInDates, ModalityManufacturer_GenericNoUniversalWildcard, ModalityManufacturer_StoreScp, - ModalityManufacturer_ClearCanvas, - ModalityManufacturer_Dcm4Chee, ModalityManufacturer_Vitrea, ModalityManufacturer_GE }; @@ -624,7 +671,9 @@ DicomRequestType_Find, DicomRequestType_Get, DicomRequestType_Move, - DicomRequestType_Store + DicomRequestType_Store, + DicomRequestType_NAction, + DicomRequestType_NEventReport }; enum TransferSyntax @@ -635,6 +684,7 @@ TransferSyntax_JpegLossless, TransferSyntax_Jpip, TransferSyntax_Mpeg2, + TransferSyntax_Mpeg4, // New in Orthanc 1.6.0 TransferSyntax_Rle }; @@ -665,6 +715,36 @@ JobStopReason_Retry }; + + // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + enum StorageCommitmentFailureReason + { + StorageCommitmentFailureReason_Success = 0, + + // A general failure in processing the operation was encountered + StorageCommitmentFailureReason_ProcessingFailure = 0x0110, + + // One or more of the elements in the Referenced SOP Instance + // Sequence was not available + StorageCommitmentFailureReason_NoSuchObjectInstance = 0x0112, + + // The SCP does not currently have enough resources to store the + // requested SOP Instance(s) + StorageCommitmentFailureReason_ResourceLimitation = 0x0213, + + // Storage Commitment has been requested for a SOP Instance with a + // SOP Class that is not supported by the SCP + StorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 0x0122, + + // The SOP Class of an element in the Referenced SOP Instance + // Sequence did not correspond to the SOP class registered for + // this SOP Instance at the SCP + StorageCommitmentFailureReason_ClassInstanceConflict = 0x0119, + + // The Transaction UID of the Storage Commitment Request is already in use + StorageCommitmentFailureReason_DuplicateTransactionUID = 0x0131 + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -751,6 +831,8 @@ const char* EnumerationToString(Endianness endianness); + const char* EnumerationToString(StorageCommitmentFailureReason reason); + Encoding StringToEncoding(const char* encoding); ResourceType StringToResourceType(const char* type); @@ -799,4 +881,11 @@ Encoding GetDefaultDicomEncoding(); void SetDefaultDicomEncoding(Encoding encoding); + + const char* GetTransferSyntaxUid(DicomTransferSyntax syntax); + + bool IsRetiredTransferSyntax(DicomTransferSyntax syntax); + + bool LookupTransferSyntax(DicomTransferSyntax& target, + const std::string& uid); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Enumerations_TransferSyntaxes.impl.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,566 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/>. + **/ + +// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py" + +namespace Orthanc +{ + const char* GetTransferSyntaxUid(DicomTransferSyntax syntax) + { + switch (syntax) + { + case DicomTransferSyntax_LittleEndianImplicit: + return "1.2.840.10008.1.2"; + + case DicomTransferSyntax_LittleEndianExplicit: + return "1.2.840.10008.1.2.1"; + + case DicomTransferSyntax_DeflatedLittleEndianExplicit: + return "1.2.840.10008.1.2.1.99"; + + case DicomTransferSyntax_BigEndianExplicit: + return "1.2.840.10008.1.2.2"; + + case DicomTransferSyntax_JPEGProcess1: + return "1.2.840.10008.1.2.4.50"; + + case DicomTransferSyntax_JPEGProcess2_4: + return "1.2.840.10008.1.2.4.51"; + + case DicomTransferSyntax_JPEGProcess3_5: + return "1.2.840.10008.1.2.4.52"; + + case DicomTransferSyntax_JPEGProcess6_8: + return "1.2.840.10008.1.2.4.53"; + + case DicomTransferSyntax_JPEGProcess7_9: + return "1.2.840.10008.1.2.4.54"; + + case DicomTransferSyntax_JPEGProcess10_12: + return "1.2.840.10008.1.2.4.55"; + + case DicomTransferSyntax_JPEGProcess11_13: + return "1.2.840.10008.1.2.4.56"; + + case DicomTransferSyntax_JPEGProcess14: + return "1.2.840.10008.1.2.4.57"; + + case DicomTransferSyntax_JPEGProcess15: + return "1.2.840.10008.1.2.4.58"; + + case DicomTransferSyntax_JPEGProcess16_18: + return "1.2.840.10008.1.2.4.59"; + + case DicomTransferSyntax_JPEGProcess17_19: + return "1.2.840.10008.1.2.4.60"; + + case DicomTransferSyntax_JPEGProcess20_22: + return "1.2.840.10008.1.2.4.61"; + + case DicomTransferSyntax_JPEGProcess21_23: + return "1.2.840.10008.1.2.4.62"; + + case DicomTransferSyntax_JPEGProcess24_26: + return "1.2.840.10008.1.2.4.63"; + + case DicomTransferSyntax_JPEGProcess25_27: + return "1.2.840.10008.1.2.4.64"; + + case DicomTransferSyntax_JPEGProcess28: + return "1.2.840.10008.1.2.4.65"; + + case DicomTransferSyntax_JPEGProcess29: + return "1.2.840.10008.1.2.4.66"; + + case DicomTransferSyntax_JPEGProcess14SV1: + return "1.2.840.10008.1.2.4.70"; + + case DicomTransferSyntax_JPEGLSLossless: + return "1.2.840.10008.1.2.4.80"; + + case DicomTransferSyntax_JPEGLSLossy: + return "1.2.840.10008.1.2.4.81"; + + case DicomTransferSyntax_JPEG2000LosslessOnly: + return "1.2.840.10008.1.2.4.90"; + + case DicomTransferSyntax_JPEG2000: + return "1.2.840.10008.1.2.4.91"; + + case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly: + return "1.2.840.10008.1.2.4.92"; + + case DicomTransferSyntax_JPEG2000Multicomponent: + return "1.2.840.10008.1.2.4.93"; + + case DicomTransferSyntax_JPIPReferenced: + return "1.2.840.10008.1.2.4.94"; + + case DicomTransferSyntax_JPIPReferencedDeflate: + return "1.2.840.10008.1.2.4.95"; + + case DicomTransferSyntax_MPEG2MainProfileAtMainLevel: + return "1.2.840.10008.1.2.4.100"; + + case DicomTransferSyntax_MPEG2MainProfileAtHighLevel: + return "1.2.840.10008.1.2.4.101"; + + case DicomTransferSyntax_MPEG4HighProfileLevel4_1: + return "1.2.840.10008.1.2.4.102"; + + case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1: + return "1.2.840.10008.1.2.4.103"; + + case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo: + return "1.2.840.10008.1.2.4.104"; + + case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo: + return "1.2.840.10008.1.2.4.105"; + + case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2: + return "1.2.840.10008.1.2.4.106"; + + case DicomTransferSyntax_HEVCMainProfileLevel5_1: + return "1.2.840.10008.1.2.4.107"; + + case DicomTransferSyntax_HEVCMain10ProfileLevel5_1: + return "1.2.840.10008.1.2.4.108"; + + case DicomTransferSyntax_RLELossless: + return "1.2.840.10008.1.2.5"; + + case DicomTransferSyntax_RFC2557MimeEncapsulation: + return "1.2.840.10008.1.2.6.1"; + + case DicomTransferSyntax_XML: + return "1.2.840.10008.1.2.6.2"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool IsRetiredTransferSyntax(DicomTransferSyntax syntax) + { + switch (syntax) + { + case DicomTransferSyntax_LittleEndianImplicit: + return false; + + case DicomTransferSyntax_LittleEndianExplicit: + return false; + + case DicomTransferSyntax_DeflatedLittleEndianExplicit: + return false; + + case DicomTransferSyntax_BigEndianExplicit: + return false; + + case DicomTransferSyntax_JPEGProcess1: + return false; + + case DicomTransferSyntax_JPEGProcess2_4: + return false; + + case DicomTransferSyntax_JPEGProcess3_5: + return true; + + case DicomTransferSyntax_JPEGProcess6_8: + return true; + + case DicomTransferSyntax_JPEGProcess7_9: + return true; + + case DicomTransferSyntax_JPEGProcess10_12: + return true; + + case DicomTransferSyntax_JPEGProcess11_13: + return true; + + case DicomTransferSyntax_JPEGProcess14: + return false; + + case DicomTransferSyntax_JPEGProcess15: + return true; + + case DicomTransferSyntax_JPEGProcess16_18: + return true; + + case DicomTransferSyntax_JPEGProcess17_19: + return true; + + case DicomTransferSyntax_JPEGProcess20_22: + return true; + + case DicomTransferSyntax_JPEGProcess21_23: + return true; + + case DicomTransferSyntax_JPEGProcess24_26: + return true; + + case DicomTransferSyntax_JPEGProcess25_27: + return true; + + case DicomTransferSyntax_JPEGProcess28: + return true; + + case DicomTransferSyntax_JPEGProcess29: + return true; + + case DicomTransferSyntax_JPEGProcess14SV1: + return false; + + case DicomTransferSyntax_JPEGLSLossless: + return false; + + case DicomTransferSyntax_JPEGLSLossy: + return false; + + case DicomTransferSyntax_JPEG2000LosslessOnly: + return false; + + case DicomTransferSyntax_JPEG2000: + return false; + + case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly: + return false; + + case DicomTransferSyntax_JPEG2000Multicomponent: + return false; + + case DicomTransferSyntax_JPIPReferenced: + return false; + + case DicomTransferSyntax_JPIPReferencedDeflate: + return false; + + case DicomTransferSyntax_MPEG2MainProfileAtMainLevel: + return false; + + case DicomTransferSyntax_MPEG2MainProfileAtHighLevel: + return false; + + case DicomTransferSyntax_MPEG4HighProfileLevel4_1: + return false; + + case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1: + return false; + + case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo: + return false; + + case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo: + return false; + + case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2: + return false; + + case DicomTransferSyntax_HEVCMainProfileLevel5_1: + return false; + + case DicomTransferSyntax_HEVCMain10ProfileLevel5_1: + return false; + + case DicomTransferSyntax_RLELossless: + return false; + + case DicomTransferSyntax_RFC2557MimeEncapsulation: + return true; + + case DicomTransferSyntax_XML: + return true; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool LookupTransferSyntax(DicomTransferSyntax& target, + const std::string& uid) + { + if (uid == "1.2.840.10008.1.2") + { + target = DicomTransferSyntax_LittleEndianImplicit; + return true; + } + + if (uid == "1.2.840.10008.1.2.1") + { + target = DicomTransferSyntax_LittleEndianExplicit; + return true; + } + + if (uid == "1.2.840.10008.1.2.1.99") + { + target = DicomTransferSyntax_DeflatedLittleEndianExplicit; + return true; + } + + if (uid == "1.2.840.10008.1.2.2") + { + target = DicomTransferSyntax_BigEndianExplicit; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.50") + { + target = DicomTransferSyntax_JPEGProcess1; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.51") + { + target = DicomTransferSyntax_JPEGProcess2_4; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.52") + { + target = DicomTransferSyntax_JPEGProcess3_5; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.53") + { + target = DicomTransferSyntax_JPEGProcess6_8; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.54") + { + target = DicomTransferSyntax_JPEGProcess7_9; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.55") + { + target = DicomTransferSyntax_JPEGProcess10_12; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.56") + { + target = DicomTransferSyntax_JPEGProcess11_13; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.57") + { + target = DicomTransferSyntax_JPEGProcess14; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.58") + { + target = DicomTransferSyntax_JPEGProcess15; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.59") + { + target = DicomTransferSyntax_JPEGProcess16_18; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.60") + { + target = DicomTransferSyntax_JPEGProcess17_19; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.61") + { + target = DicomTransferSyntax_JPEGProcess20_22; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.62") + { + target = DicomTransferSyntax_JPEGProcess21_23; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.63") + { + target = DicomTransferSyntax_JPEGProcess24_26; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.64") + { + target = DicomTransferSyntax_JPEGProcess25_27; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.65") + { + target = DicomTransferSyntax_JPEGProcess28; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.66") + { + target = DicomTransferSyntax_JPEGProcess29; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.70") + { + target = DicomTransferSyntax_JPEGProcess14SV1; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.80") + { + target = DicomTransferSyntax_JPEGLSLossless; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.81") + { + target = DicomTransferSyntax_JPEGLSLossy; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.90") + { + target = DicomTransferSyntax_JPEG2000LosslessOnly; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.91") + { + target = DicomTransferSyntax_JPEG2000; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.92") + { + target = DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.93") + { + target = DicomTransferSyntax_JPEG2000Multicomponent; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.94") + { + target = DicomTransferSyntax_JPIPReferenced; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.95") + { + target = DicomTransferSyntax_JPIPReferencedDeflate; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.100") + { + target = DicomTransferSyntax_MPEG2MainProfileAtMainLevel; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.101") + { + target = DicomTransferSyntax_MPEG2MainProfileAtHighLevel; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.102") + { + target = DicomTransferSyntax_MPEG4HighProfileLevel4_1; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.103") + { + target = DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.104") + { + target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.105") + { + target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.106") + { + target = DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.107") + { + target = DicomTransferSyntax_HEVCMainProfileLevel5_1; + return true; + } + + if (uid == "1.2.840.10008.1.2.4.108") + { + target = DicomTransferSyntax_HEVCMain10ProfileLevel5_1; + return true; + } + + if (uid == "1.2.840.10008.1.2.5") + { + target = DicomTransferSyntax_RLELossless; + return true; + } + + if (uid == "1.2.840.10008.1.2.6.1") + { + target = DicomTransferSyntax_RFC2557MimeEncapsulation; + return true; + } + + if (uid == "1.2.840.10008.1.2.6.2") + { + target = DicomTransferSyntax_XML; + return true; + } + + return false; + } +}
--- a/Core/FileBuffer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileBuffer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileBuffer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileBuffer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/FileInfo.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/FileInfo.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/FilesystemStorage.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/FilesystemStorage.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/FilesystemStorage.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/FilesystemStorage.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/IStorageArea.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/IStorageArea.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/MemoryStorageArea.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/MemoryStorageArea.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/MemoryStorageArea.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/MemoryStorageArea.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/StorageAccessor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/StorageAccessor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "../PrecompiledHeaders.h" #include "StorageAccessor.h" +#include "../Compatibility.h" #include "../Compression/ZlibCompressor.h" #include "../MetricsRegistry.h" #include "../OrthancException.h" @@ -54,7 +55,7 @@ class StorageAccessor::MetricsTimer : public boost::noncopyable { private: - std::auto_ptr<MetricsRegistry::Timer> timer_; + std::unique_ptr<MetricsRegistry::Timer> timer_; public: MetricsTimer(StorageAccessor& that,
--- a/Core/FileStorage/StorageAccessor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/FileStorage/StorageAccessor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpClient.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpClient.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -45,7 +45,6 @@ #include <boost/algorithm/string/predicate.hpp> #include <boost/thread/mutex.hpp> - // Default timeout = 60 seconds (in Orthanc <= 1.5.6, it was 10 seconds) static const unsigned int DEFAULT_HTTP_TIMEOUT = 60; @@ -209,7 +208,8 @@ { private: HttpClient::IRequestBody* body_; - std::string buffer_; + std::string sourceBuffer_; + size_t sourceBufferTransmittedSize_; size_t CallbackInternal(char* curlBuffer, size_t curlBufferSize) @@ -225,43 +225,64 @@ } // Read chunks from the body stream so as to fill the target buffer - std::string chunk; + size_t curlBufferFilledSize = 0; + size_t sourceRemainingSize = sourceBuffer_.size() - sourceBufferTransmittedSize_; + bool hasMore = true; - while (buffer_.size() < curlBufferSize && - body_->ReadNextChunk(chunk)) + while (sourceRemainingSize < curlBufferSize && hasMore) { - buffer_ += chunk; + if (sourceRemainingSize > 0) + { + // transmit the end of current source buffer + memcpy(curlBuffer + curlBufferFilledSize, + sourceBuffer_.data() + sourceBufferTransmittedSize_, sourceRemainingSize); + + curlBufferFilledSize += sourceRemainingSize; + } + + // start filling a new source buffer + sourceBufferTransmittedSize_ = 0; + sourceBuffer_.clear(); + + hasMore = body_->ReadNextChunk(sourceBuffer_); + + sourceRemainingSize = sourceBuffer_.size(); } - size_t s = std::min(buffer_.size(), curlBufferSize); - - if (s != 0) + if (sourceRemainingSize > 0 && + curlBufferSize > curlBufferFilledSize) { - memcpy(curlBuffer, buffer_.c_str(), s); + size_t s = std::min(sourceRemainingSize, curlBufferSize - curlBufferFilledSize); - // Remove the bytes that were actually sent from the buffer - buffer_.erase(0, s); + memcpy(curlBuffer + curlBufferFilledSize, + sourceBuffer_.data() + sourceBufferTransmittedSize_, s); + + sourceBufferTransmittedSize_ += s; + curlBufferFilledSize += s; } - return s; + return curlBufferFilledSize; } public: CurlRequestBody() : - body_(NULL) + body_(NULL), + sourceBufferTransmittedSize_(0) { } void SetBody(HttpClient::IRequestBody& body) { body_ = &body; - buffer_.clear(); + sourceBufferTransmittedSize_ = 0; + sourceBuffer_.clear(); } void Clear() { body_ = NULL; - buffer_.clear(); + sourceBufferTransmittedSize_ = 0; + sourceBuffer_.clear(); } bool IsValid() const
--- a/Core/HttpClient.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpClient.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/BufferHttpSender.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/BufferHttpSender.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/BufferHttpSender.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/BufferHttpSender.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -51,7 +51,7 @@ const std::string& baseUri, EmbeddedResources::DirectoryResourceId resourceId); - virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target, + virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username,
--- a/Core/HttpServer/FilesystemHttpHandler.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/FilesystemHttpHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -53,7 +53,7 @@ FilesystemHttpHandler(const std::string& baseUri, const std::string& root); - virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target, + virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username,
--- a/Core/HttpServer/FilesystemHttpSender.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/FilesystemHttpSender.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpContentNegociation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpContentNegociation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -170,18 +170,22 @@ } - void HttpContentNegociation::SelectBestMatch(std::auto_ptr<Reference>& best, + void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& best, const Handler& handler, const std::string& type, const std::string& subtype, float quality) { - std::auto_ptr<Reference> match(new Reference(handler, type, subtype, quality)); + std::unique_ptr<Reference> match(new Reference(handler, type, subtype, quality)); if (best.get() == NULL || *best < *match) { - best = match; +#if __cplusplus < 201103L + best.reset(match.release()); +#else + best = std::move(match); +#endif } } @@ -227,7 +231,7 @@ Tokens mediaRanges; Toolbox::TokenizeString(mediaRanges, accept, ','); - std::auto_ptr<Reference> bestMatch; + std::unique_ptr<Reference> bestMatch; for (Tokens::const_iterator it = mediaRanges.begin(); it != mediaRanges.end(); ++it)
--- a/Core/HttpServer/HttpContentNegociation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpContentNegociation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,6 +32,8 @@ #pragma once +#include "../Compatibility.h" + #include <memory> #include <boost/noncopyable.hpp> #include <map> @@ -95,7 +97,7 @@ static float GetQuality(const Tokens& parameters); - static void SelectBestMatch(std::auto_ptr<Reference>& best, + static void SelectBestMatch(std::unique_ptr<Reference>& best, const Handler& handler, const std::string& type, const std::string& subtype,
--- a/Core/HttpServer/HttpFileSender.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpFileSender.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpFileSender.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpFileSender.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpOutput.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpOutput.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -463,6 +463,21 @@ } boundary = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid(); + + /** + * Fix for issue #165: "Encapsulation boundaries must not appear + * within the encapsulations, and must be no longer than 70 + * characters, not counting the two leading hyphens." + * https://tools.ietf.org/html/rfc1521 + * https://bitbucket.org/sjodogne/orthanc/issues/165/ + **/ + if (boundary.size() != 36 + 1 + 36) // one UUID contains 36 characters + { + throw OrthancException(ErrorCode_InternalError); + } + + boundary = boundary.substr(0, 70); + contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary); }
--- a/Core/HttpServer/HttpOutput.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpOutput.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpServer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpServer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -61,6 +61,7 @@ #include <string.h> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> #include <iostream> #include <string.h> #include <stdio.h> @@ -852,7 +853,7 @@ if (!isMultipartForm) { - std::auto_ptr<IHttpHandler::IChunkedRequestReader> stream; + std::unique_ptr<IHttpHandler::IChunkedRequestReader> stream; if (server.HasHandler()) { @@ -968,10 +969,15 @@ throw OrthancException(ErrorCode_BadParameterType, "Syntax error in some user-supplied data"); } + catch (boost::filesystem::filesystem_error& e) + { + throw OrthancException(ErrorCode_InternalError, + "Error while accessing the filesystem: " + e.path1().string()); + } catch (std::runtime_error&) { - // Presumably an error while parsing the JSON body - throw OrthancException(ErrorCode_BadRequest); + throw OrthancException(ErrorCode_BadRequest, + "Presumably an error while parsing the JSON body"); } catch (std::bad_alloc&) {
--- a/Core/HttpServer/HttpServer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpServer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpStreamTranscoder.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpStreamTranscoder.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpStreamTranscoder.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpStreamTranscoder.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,7 +35,9 @@ #include "BufferHttpSender.h" -#include <memory> // For std::auto_ptr +#include "../Compatibility.h" + +#include <memory> // For std::unique_ptr namespace Orthanc { @@ -49,7 +51,7 @@ uint64_t currentChunkOffset_; bool ready_; - std::auto_ptr<BufferHttpSender> uncompressed_; + std::unique_ptr<BufferHttpSender> uncompressed_; void ReadSource(std::string& buffer);
--- a/Core/HttpServer/HttpToolbox.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpToolbox.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpToolbox.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/HttpToolbox.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/IHttpHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/IHttpHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "../Compatibility.h" #include "../Toolbox.h" #include "HttpOutput.h" @@ -73,7 +74,7 @@ * This function allows to deal with chunked transfers (new in * Orthanc 1.5.7). It is only called if "method" is POST or PUT. **/ - virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target, + virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username,
--- a/Core/HttpServer/IHttpOutputStream.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/IHttpOutputStream.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/IHttpStreamAnswer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/IHttpStreamAnswer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/IIncomingHttpRequestFilter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/IIncomingHttpRequestFilter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/MultipartStreamReader.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/MultipartStreamReader.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/MultipartStreamReader.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/MultipartStreamReader.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringHttpOutput.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/StringHttpOutput.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringHttpOutput.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/StringHttpOutput.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringMatcher.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/StringMatcher.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringMatcher.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/HttpServer/StringMatcher.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/IDynamicObject.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/IDynamicObject.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/Font.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/Font.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -103,7 +103,7 @@ throw OrthancException(ErrorCode_BadFont); } - std::auto_ptr<Character> c(new Character); + std::unique_ptr<Character> c(new Character); c->advance_ = info["Advance"].asUInt(); c->height_ = info["Height"].asUInt(); @@ -407,7 +407,7 @@ unsigned int width, height; ComputeTextExtent(width, height, utf8); - std::auto_ptr<ImageAccessor> target(new Image(format, width, height, false)); + std::unique_ptr<ImageAccessor> target(new Image(format, width, height, false)); ImageProcessing::Set(*target, 0, 0, 0, 255); Draw(*target, utf8, 0, 0, r, g, b); @@ -420,7 +420,7 @@ unsigned int width, height; ComputeTextExtent(width, height, utf8); - std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, width, height, false)); + std::unique_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, width, height, false)); ImageProcessing::Set(*target, 0); Draw(*target, utf8, 0, 0, 255);
--- a/Core/Images/Font.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/Font.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/FontRegistry.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/FontRegistry.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -51,7 +51,7 @@ void FontRegistry::AddFromMemory(const std::string& font) { - std::auto_ptr<Font> f(new Font); + std::unique_ptr<Font> f(new Font); f->LoadFromMemory(font); fonts_.push_back(f.release()); } @@ -60,7 +60,7 @@ #if ORTHANC_SANDBOXED == 0 void FontRegistry::AddFromFile(const std::string& path) { - std::auto_ptr<Font> f(new Font); + std::unique_ptr<Font> f(new Font); f->LoadFromFile(path); fonts_.push_back(f.release()); }
--- a/Core/Images/FontRegistry.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/FontRegistry.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/IImageWriter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/IImageWriter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/IImageWriter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/IImageWriter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/Image.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/Image.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #include "Image.h" +#include "../Compatibility.h" #include "ImageProcessing.h" #include <memory> @@ -54,7 +55,7 @@ Image* Image::Clone(const ImageAccessor& source) { - std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false)); + std::unique_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false)); ImageProcessing::Copy(*target, source); return target.release(); }
--- a/Core/Images/Image.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/Image.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageAccessor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/ImageAccessor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageAccessor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/ImageAccessor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageBuffer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/ImageBuffer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageBuffer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/ImageBuffer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageProcessing.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/ImageProcessing.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -39,6 +39,17 @@ #include "PixelTraits.h" #include "../OrthancException.h" +#ifdef __EMSCRIPTEN__ +/* + Avoid this error: + ----------------- + .../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits<long long>::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion] + .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:333:28: note: in instantiation of function template specialization 'boost::math::llround<float>' requested here + .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:1006:9: note: in instantiation of function template specialization 'Orthanc::MultiplyConstantInternal<unsigned char, true>' requested here +*/ +#pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" +#endif + #include <boost/math/special_functions/round.hpp> #include <cassert> @@ -56,6 +67,11 @@ return sqrt(dx * dx + dy * dy); } + double ImageProcessing::ImagePoint::GetDistanceToLine(double a, double b, double c) const // where ax + by + c = 0 is the equation of the line + { + return std::abs(a * static_cast<double>(GetX()) + b * static_cast<double>(GetY()) + c) / pow(a * a + b * b, 0.5); + } + template <typename TargetType, typename SourceType> static void ConvertInternal(ImageAccessor& target, const ImageAccessor& source) @@ -369,28 +385,47 @@ } - template <typename PixelType, - bool UseRound> - static void ShiftScaleInternal(ImageAccessor& image, - float offset, - float scaling, - const PixelType LowestValue = std::numeric_limits<PixelType>::min()) + // Computes "a * x + b" at each pixel => Note that this is not the + // same convention as in "ShiftScale()" + template <typename TargetType, + typename SourceType, + bool UseRound, + bool Invert> + static void ShiftScaleInternal(ImageAccessor& target, + const ImageAccessor& source, + float a, + float b, + const TargetType LowestValue) + // This function can be applied inplace (source == target) { - const PixelType minPixelValue = LowestValue; - const PixelType maxPixelValue = std::numeric_limits<PixelType>::max(); + if (source.GetWidth() != target.GetWidth() || + source.GetHeight() != target.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (&source == &target && + source.GetFormat() != target.GetFormat()) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + const TargetType minPixelValue = LowestValue; + const TargetType maxPixelValue = std::numeric_limits<TargetType>::max(); const float minFloatValue = static_cast<float>(LowestValue); const float maxFloatValue = static_cast<float>(maxPixelValue); - const unsigned int height = image.GetHeight(); - const unsigned int width = image.GetWidth(); + const unsigned int height = target.GetHeight(); + const unsigned int width = target.GetWidth(); for (unsigned int y = 0; y < height; y++) { - PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + TargetType* p = reinterpret_cast<TargetType*>(target.GetRow(y)); + const SourceType* q = reinterpret_cast<const SourceType*>(source.GetRow(y)); - for (unsigned int x = 0; x < width; x++, p++) + for (unsigned int x = 0; x < width; x++, p++, q++) { - float v = (static_cast<float>(*p) + offset) * scaling; + float v = a * static_cast<float>(*q) + b; if (v >= maxFloatValue) { @@ -403,16 +438,56 @@ else if (UseRound) { // The "round" operation is very costly - *p = static_cast<PixelType>(boost::math::iround(v)); + *p = static_cast<TargetType>(boost::math::iround(v)); } else { - *p = static_cast<PixelType>(v); + *p = static_cast<TargetType>(std::floor(v)); + } + + if (Invert) + { + *p = maxPixelValue - *p; } } } } + template <typename PixelType> + static void ShiftRightInternal(ImageAccessor& image, + unsigned int shift) + { + const unsigned int height = image.GetHeight(); + const unsigned int width = image.GetWidth(); + + for (unsigned int y = 0; y < height; y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++) + { + *p = *p >> shift; + } + } + } + + template <typename PixelType> + static void ShiftLeftInternal(ImageAccessor& image, + unsigned int shift) + { + const unsigned int height = image.GetHeight(); + const unsigned int width = image.GetWidth(); + + for (unsigned int y = 0; y < height; y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++) + { + *p = *p << shift; + } + } + } void ImageProcessing::Copy(ImageAccessor& target, const ImageAccessor& source) @@ -438,6 +513,104 @@ } } + template <typename TargetType, typename SourceType> + static void ApplyWindowingInternal(ImageAccessor& target, + const ImageAccessor& source, + float windowCenter, + float windowWidth, + float rescaleSlope, + float rescaleIntercept, + bool invert) + { + assert(sizeof(SourceType) == source.GetBytesPerPixel() && + sizeof(TargetType) == target.GetBytesPerPixel()); + + // WARNING - "::min()" should be replaced by "::lowest()" if + // dealing with float or double (which is not the case so far) + assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" + const TargetType minTargetValue = std::numeric_limits<TargetType>::min(); + const TargetType maxTargetValue = std::numeric_limits<TargetType>::max(); + const float maxFloatValue = static_cast<float>(maxTargetValue); + + const float windowIntercept = windowCenter - windowWidth / 2.0f; + const float windowSlope = (maxFloatValue + 1.0f) / windowWidth; + + const float a = rescaleSlope * windowSlope; + const float b = (rescaleIntercept - windowIntercept) * windowSlope; + + if (invert) + { + ShiftScaleInternal<TargetType, SourceType, false, true>(target, source, a, b, minTargetValue); + } + else + { + ShiftScaleInternal<TargetType, SourceType, false, false>(target, source, a, b, minTargetValue); + } + } + + void ImageProcessing::ApplyWindowing_Deprecated(ImageAccessor& target, + const ImageAccessor& source, + float windowCenter, + float windowWidth, + float rescaleSlope, + float rescaleIntercept, + bool invert) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + switch (source.GetFormat()) + { + case Orthanc::PixelFormat_Float32: + { + switch (target.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + ApplyWindowingInternal<uint8_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); + break; + case Orthanc::PixelFormat_Grayscale16: + ApplyWindowingInternal<uint16_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); + break; + default: + throw OrthancException(ErrorCode_NotImplemented); + } + };break; + case Orthanc::PixelFormat_Grayscale8: + { + switch (target.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + ApplyWindowingInternal<uint8_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); + break; + case Orthanc::PixelFormat_Grayscale16: + ApplyWindowingInternal<uint16_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); + break; + default: + throw OrthancException(ErrorCode_NotImplemented); + } + };break; + case Orthanc::PixelFormat_Grayscale16: + { + switch (target.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + ApplyWindowingInternal<uint8_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); + break; + case Orthanc::PixelFormat_Grayscale16: + ApplyWindowingInternal<uint16_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); + break; + default: + throw OrthancException(ErrorCode_NotImplemented); + } + };break; + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + void ImageProcessing::Convert(ImageAccessor& target, const ImageAccessor& source) @@ -768,6 +941,29 @@ return; } + if ((target.GetFormat() == PixelFormat_BGRA32 && + source.GetFormat() == PixelFormat_RGBA32) + || (target.GetFormat() == PixelFormat_RGBA32 && + source.GetFormat() == PixelFormat_BGRA32)) + { + for (unsigned int y = 0; y < height; y++) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + for (unsigned int x = 0; x < width; x++) + { + q[0] = p[2]; + q[1] = p[1]; + q[2] = p[0]; + q[3] = p[3]; + p += 4; + q += 4; + } + } + + return; + } + if (target.GetFormat() == PixelFormat_RGB24 && source.GetFormat() == PixelFormat_RGB48) { @@ -909,6 +1105,63 @@ } } + void ImageProcessing::Set(ImageAccessor& image, + uint8_t red, + uint8_t green, + uint8_t blue, + ImageAccessor& alpha) + { + uint8_t p[4]; + + if (alpha.GetWidth() != image.GetWidth() || alpha.GetHeight() != image.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (alpha.GetFormat() != PixelFormat_Grayscale8) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + switch (image.GetFormat()) + { + case PixelFormat_RGBA32: + p[0] = red; + p[1] = green; + p[2] = blue; + break; + + case PixelFormat_BGRA32: + p[0] = blue; + p[1] = green; + p[2] = red; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + const unsigned int width = image.GetWidth(); + const unsigned int height = image.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y)); + uint8_t* a = reinterpret_cast<uint8_t*>(alpha.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + for (unsigned int i = 0; i < 3; i++) + { + q[i] = p[i]; + } + q[3] = *a; + q += 4; + ++a; + } + } + } + void ImageProcessing::ShiftRight(ImageAccessor& image, unsigned int shift) @@ -921,9 +1174,52 @@ return; } - throw OrthancException(ErrorCode_NotImplemented); + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + ShiftRightInternal<uint8_t>(image, shift); + break; + } + + case PixelFormat_Grayscale16: + { + ShiftRightInternal<uint16_t>(image, shift); + break; + } + default: + throw OrthancException(ErrorCode_NotImplemented); + } } + void ImageProcessing::ShiftLeft(ImageAccessor& image, + unsigned int shift) + { + if (image.GetWidth() == 0 || + image.GetHeight() == 0 || + shift == 0) + { + // Nothing to do + return; + } + + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + ShiftLeftInternal<uint8_t>(image, shift); + break; + } + + case PixelFormat_Grayscale16: + { + ShiftLeftInternal<uint16_t>(image, shift); + break; + } + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue, int64_t& maxValue, @@ -1076,49 +1372,55 @@ float scaling, bool useRound) { + // Rewrite "(x + offset) * scaling" as "a * x + b" + + const float a = scaling; + const float b = offset * scaling; + switch (image.GetFormat()) { case PixelFormat_Grayscale8: if (useRound) { - ShiftScaleInternal<uint8_t, true>(image, offset, scaling); + ShiftScaleInternal<uint8_t, uint8_t, true, false>(image, image, a, b, std::numeric_limits<uint8_t>::min()); } else { - ShiftScaleInternal<uint8_t, false>(image, offset, scaling); + ShiftScaleInternal<uint8_t, uint8_t, false, false>(image, image, a, b, std::numeric_limits<uint8_t>::min()); } return; case PixelFormat_Grayscale16: if (useRound) { - ShiftScaleInternal<uint16_t, true>(image, offset, scaling); + ShiftScaleInternal<uint16_t, uint16_t, true, false>(image, image, a, b, std::numeric_limits<uint16_t>::min()); } else { - ShiftScaleInternal<uint16_t, false>(image, offset, scaling); + ShiftScaleInternal<uint16_t, uint16_t, false, false>(image, image, a, b, std::numeric_limits<uint16_t>::min()); } return; case PixelFormat_SignedGrayscale16: if (useRound) { - ShiftScaleInternal<int16_t, true>(image, offset, scaling); + ShiftScaleInternal<int16_t, int16_t, true, false>(image, image, a, b, std::numeric_limits<int16_t>::min()); } else { - ShiftScaleInternal<int16_t, false>(image, offset, scaling); + ShiftScaleInternal<int16_t, int16_t, false, false>(image, image, a, b, std::numeric_limits<int16_t>::min()); } return; case PixelFormat_Float32: + // "::min()" must be replaced by "::lowest()" or "-::max()" if dealing with float or double. if (useRound) { - ShiftScaleInternal<float, true>(image, offset, scaling, -std::numeric_limits<float>::max()); + ShiftScaleInternal<float, float, true, false>(image, image, a, b, -std::numeric_limits<float>::max()); } else { - ShiftScaleInternal<float, false>(image, offset, scaling, -std::numeric_limits<float>::max()); + ShiftScaleInternal<float, float, false, false>(image, image, a, b, -std::numeric_limits<float>::max()); } return; @@ -1128,6 +1430,46 @@ } + void ImageProcessing::ShiftScale(ImageAccessor& target, + const ImageAccessor& source, + float offset, + float scaling, + bool useRound) + { + // Rewrite "(x + offset) * scaling" as "a * x + b" + + const float a = scaling; + const float b = offset * scaling; + + switch (target.GetFormat()) + { + case PixelFormat_Grayscale8: + + switch (source.GetFormat()) + { + case PixelFormat_Float32: + if (useRound) + { + ShiftScaleInternal<uint8_t, float, true, false>( + target, source, a, b, std::numeric_limits<uint8_t>::min()); + } + else + { + ShiftScaleInternal<uint8_t, float, false, false>( + target, source, a, b, std::numeric_limits<uint8_t>::min()); + } + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + void ImageProcessing::Invert(ImageAccessor& image, int64_t maxValue) { const unsigned int width = image.GetWidth(); @@ -1626,7 +1968,7 @@ for (unsigned int x = 0; x < targetWidth; x++) { - int sourceX = std::floor((static_cast<float>(x) + 0.5f) * scaleX); + int sourceX = static_cast<int>(std::floor((static_cast<float>(x) + 0.5f) * scaleX)); if (sourceX < 0) { sourceX = 0; // Should never happen @@ -1643,7 +1985,7 @@ for (unsigned int y = 0; y < targetHeight; y++) { - int sourceY = std::floor((static_cast<float>(y) + 0.5f) * scaleY); + int sourceY = static_cast<int>(std::floor((static_cast<float>(y) + 0.5f) * scaleY)); if (sourceY < 0) { sourceY = 0; // Should never happen @@ -1692,13 +2034,17 @@ Copy(target, source); return; } - + switch (source.GetFormat()) { case PixelFormat_Grayscale8: ResizeInternal<PixelFormat_Grayscale8>(target, source); break; + case PixelFormat_Float32: + ResizeInternal<PixelFormat_Float32>(target, source); + break; + case PixelFormat_RGB24: ResizeInternal<PixelFormat_RGB24>(target, source); break; @@ -1712,8 +2058,8 @@ ImageAccessor* ImageProcessing::Halve(const ImageAccessor& source, bool forceMinimalPitch) { - std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth() / 2, - source.GetHeight() / 2, forceMinimalPitch)); + std::unique_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth() / 2, + source.GetHeight() / 2, forceMinimalPitch)); Resize(*target, source); return target.release(); } @@ -1921,7 +2267,8 @@ } // Deal with the right border - for (unsigned int x = horizontalAnchor + width - horizontal.size() + 1; x < width; x++) + for (unsigned int x = static_cast<unsigned int>( + horizontalAnchor + width - horizontal.size() + 1); x < width; x++) { for (unsigned int c = 0; c < ChannelsCount; c++, p++) { @@ -1953,7 +2300,7 @@ } else { - rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(y + k - verticalAnchor)); + rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(static_cast<unsigned int>(y + k - verticalAnchor))); } } @@ -2063,4 +2410,57 @@ SeparableConvolution(image, kernel, 2, kernel, 2); } + + + void ImageProcessing::FitSize(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() == 0 || + target.GetHeight() == 0) + { + return; + } + + if (source.GetWidth() == target.GetWidth() && + source.GetHeight() == target.GetHeight()) + { + Copy(target, source); + return; + } + + Set(target, 0); + + // Preserve the aspect ratio + float cw = static_cast<float>(source.GetWidth()); + float ch = static_cast<float>(source.GetHeight()); + float r = std::min( + static_cast<float>(target.GetWidth()) / cw, + static_cast<float>(target.GetHeight()) / ch); + + unsigned int sw = std::min(static_cast<unsigned int>(boost::math::iround(cw * r)), target.GetWidth()); + unsigned int sh = std::min(static_cast<unsigned int>(boost::math::iround(ch * r)), target.GetHeight()); + Image resized(target.GetFormat(), sw, sh, false); + + //ImageProcessing::SmoothGaussian5x5(source); + ImageProcessing::Resize(resized, source); + + assert(target.GetWidth() >= resized.GetWidth() && + target.GetHeight() >= resized.GetHeight()); + unsigned int offsetX = (target.GetWidth() - resized.GetWidth()) / 2; + unsigned int offsetY = (target.GetHeight() - resized.GetHeight()) / 2; + + ImageAccessor region; + target.GetRegion(region, offsetX, offsetY, resized.GetWidth(), resized.GetHeight()); + ImageProcessing::Copy(region, resized); + } + + + ImageAccessor* ImageProcessing::FitSize(const ImageAccessor& source, + unsigned int width, + unsigned int height) + { + std::unique_ptr<ImageAccessor> target(new Image(source.GetFormat(), width, height, false)); + FitSize(*target, source); + return target.release(); + } }
--- a/Core/Images/ImageProcessing.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/ImageProcessing.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -72,6 +72,8 @@ } double GetDistanceTo(const ImagePoint& other) const; + + double GetDistanceToLine(double a, double b, double c) const; // where ax + by + c = 0 is the equation of the line }; void Copy(ImageAccessor& target, @@ -80,6 +82,14 @@ void Convert(ImageAccessor& target, const ImageAccessor& source); + void ApplyWindowing_Deprecated(ImageAccessor& target, + const ImageAccessor& source, + float windowCenter, + float windowWidth, + float rescaleSlope, + float rescaleIntercept, + bool invert); + void Set(ImageAccessor& image, int64_t value); @@ -89,9 +99,18 @@ uint8_t blue, uint8_t alpha); + void Set(ImageAccessor& image, + uint8_t red, + uint8_t green, + uint8_t blue, + ImageAccessor& alpha); + void ShiftRight(ImageAccessor& target, unsigned int shift); + void ShiftLeft(ImageAccessor& target, + unsigned int shift); + void GetMinMaxIntegerValue(int64_t& minValue, int64_t& maxValue, const ImageAccessor& image); @@ -108,12 +127,18 @@ float factor, bool useRound); - // "useRound" is expensive + // Computes "(x + offset) * scaling" inplace. "useRound" is expensive. void ShiftScale(ImageAccessor& image, float offset, float scaling, bool useRound); + void ShiftScale(ImageAccessor& target, + const ImageAccessor& source, + float offset, + float scaling, + bool useRound); + void Invert(ImageAccessor& image); void Invert(ImageAccessor& image, int64_t maxValue); @@ -156,5 +181,12 @@ size_t verticalAnchor); void SmoothGaussian5x5(ImageAccessor& image); + + void FitSize(ImageAccessor& target, + const ImageAccessor& source); + + ImageAccessor* FitSize(const ImageAccessor& source, + unsigned int width, + unsigned int height); } }
--- a/Core/Images/ImageTraits.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/ImageTraits.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegErrorManager.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/JpegErrorManager.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegErrorManager.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/JpegErrorManager.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegReader.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/JpegReader.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegReader.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/JpegReader.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegWriter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/JpegWriter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegWriter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/JpegWriter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamReader.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PamReader.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamReader.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PamReader.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamWriter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PamWriter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamWriter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PamWriter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PixelTraits.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PixelTraits.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -383,6 +383,20 @@ } ORTHANC_FORCE_INLINE + static void SetMinValue(PixelType& target) + { + // std::numeric_limits<float>::lowest is not supported on + // all compilers (for instance, Visual Studio 9.0 2008) + target = -std::numeric_limits<float>::max(); + } + + ORTHANC_FORCE_INLINE + static void SetMaxValue(PixelType& target) + { + target = std::numeric_limits<float>::max(); + } + + ORTHANC_FORCE_INLINE static void FloatToPixel(PixelType& target, float value) {
--- a/Core/Images/PngReader.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PngReader.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PngReader.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PngReader.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PngWriter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PngWriter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PngWriter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Images/PngWriter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/GenericJobUnserializer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/GenericJobUnserializer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/GenericJobUnserializer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/GenericJobUnserializer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/IJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/IJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -50,7 +50,7 @@ // Method called once the job enters the jobs engine virtual void Start() = 0; - virtual JobStepResult Step() = 0; + virtual JobStepResult Step(const std::string& jobId) = 0; // Method called once the job is resubmitted after a failure virtual void Reset() = 0;
--- a/Core/JobsEngine/IJobUnserializer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/IJobUnserializer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobInfo.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobInfo.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,6 +32,22 @@ #include "../PrecompiledHeaders.h" + +#ifdef __EMSCRIPTEN__ +/* +Avoid this error: + +.../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits<long long>::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion] +.../boost/math/special_functions/round.hpp:125:11: note: in instantiation of function template specialization 'boost::math::llround<float, boost::math::policies::policy<boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy> >' requested here +.../orthanc/Core/JobsEngine/JobInfo.cpp:69:44: note: in instantiation of function template specialization 'boost::math::llround<float>' requested here + +.../boost/math/special_functions/round.hpp:86:12: warning: implicit conversion from 'std::__2::numeric_limits<int>::type' (aka 'int') to 'float' changes value from 2147483647 to 2147483648 [-Wimplicit-int-float-conversion] +.../boost/math/special_functions/round.hpp:93:11: note: in instantiation of function template specialization 'boost::math::iround<float, boost::math::policies::policy<boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy> >' requested here +.../orthanc/Core/JobsEngine/JobInfo.cpp:133:39: note: in instantiation of function template specialization 'boost::math::iround<float>' requested here +*/ +#pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" +#endif + #include "JobInfo.h" #include "../OrthancException.h"
--- a/Core/JobsEngine/JobInfo.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobInfo.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStatus.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobStatus.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStatus.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobStatus.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStepResult.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobStepResult.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStepResult.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobStepResult.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobsEngine.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobsEngine.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -71,7 +71,7 @@ try { - result = running.GetJob().Step(); + result = running.GetJob().Step(running.GetId()); } catch (OrthancException& e) {
--- a/Core/JobsEngine/JobsEngine.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobsEngine.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,11 +35,13 @@ #include "JobsRegistry.h" +#include "../Compatibility.h" + #include <boost/thread.hpp> namespace Orthanc { - class JobsEngine + class JobsEngine : public boost::noncopyable { private: enum State @@ -52,7 +54,7 @@ boost::mutex stateMutex_; State state_; - std::auto_ptr<JobsRegistry> registry_; + std::unique_ptr<JobsRegistry> registry_; boost::thread retryHandler_; unsigned int threadSleep_; std::vector<boost::thread*> workers_;
--- a/Core/JobsEngine/JobsRegistry.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobsRegistry.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -58,7 +58,7 @@ std::string id_; JobState state_; std::string jobType_; - std::auto_ptr<IJob> job_; + std::unique_ptr<IJob> job_; int priority_; // "+inf()" means highest priority boost::posix_time::ptime creationTime_; boost::posix_time::ptime lastStateChangeTime_; @@ -669,7 +669,7 @@ throw OrthancException(ErrorCode_NullPointer); } - std::auto_ptr<JobHandler> protection(handler); + std::unique_ptr<JobHandler> protection(handler); { boost::mutex::scoped_lock lock(mutex_); @@ -1396,7 +1396,7 @@ for (Json::Value::Members::const_iterator it = members.begin(); it != members.end(); ++it) { - std::auto_ptr<JobHandler> job; + std::unique_ptr<JobHandler> job; try {
--- a/Core/JobsEngine/JobsRegistry.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/JobsRegistry.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/IJobOperation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/IJobOperation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/JobOperationValue.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/JobOperationValue.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/JobOperationValues.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/JobOperationValues.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -129,7 +129,7 @@ throw OrthancException(ErrorCode_BadFileFormat); } - std::auto_ptr<JobOperationValues> result(new JobOperationValues); + std::unique_ptr<JobOperationValues> result(new JobOperationValues); result->Reserve(source.size());
--- a/Core/JobsEngine/Operations/JobOperationValues.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/JobOperationValues.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/LogJobOperation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/LogJobOperation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/LogJobOperation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/LogJobOperation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/NullOperationValue.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/NullOperationValue.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -56,12 +56,12 @@ class SequenceOfOperationsJob::Operation : public boost::noncopyable { private: - size_t index_; - std::auto_ptr<IJobOperation> operation_; - std::auto_ptr<JobOperationValues> originalInputs_; - std::auto_ptr<JobOperationValues> workInputs_; - std::list<Operation*> nextOperations_; - size_t currentInput_; + size_t index_; + std::unique_ptr<IJobOperation> operation_; + std::unique_ptr<JobOperationValues> originalInputs_; + std::unique_ptr<JobOperationValues> workInputs_; + std::list<Operation*> nextOperations_; + size_t currentInput_; public: Operation(size_t index, @@ -319,7 +319,7 @@ } - JobStepResult SequenceOfOperationsJob::Step() + JobStepResult SequenceOfOperationsJob::Step(const std::string& jobId) { boost::mutex::scoped_lock lock(mutex_);
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -126,30 +126,30 @@ size_t output); }; - virtual void Start() + virtual void Start() ORTHANC_OVERRIDE { } - virtual JobStepResult Step(); + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE; - virtual void Reset(); + virtual void Reset() ORTHANC_OVERRIDE; - virtual void Stop(JobStopReason reason); + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE; - virtual float GetProgress(); + virtual float GetProgress() ORTHANC_OVERRIDE; - virtual void GetJobType(std::string& target) + virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE { target = "SequenceOfOperations"; } - virtual void GetPublicContent(Json::Value& value); + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; - virtual bool Serialize(Json::Value& value); + virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE; virtual bool GetOutput(std::string& output, MimeType& mime, - const std::string& key) + const std::string& key) ORTHANC_OVERRIDE { return false; }
--- a/Core/JobsEngine/Operations/StringOperationValue.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/Operations/StringOperationValue.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/SetOfCommandsJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/SetOfCommandsJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -145,7 +145,7 @@ } - JobStepResult SetOfCommandsJob::Step() + JobStepResult SetOfCommandsJob::Step(const std::string& jobId) { if (!started_) { @@ -169,7 +169,7 @@ try { // Not at the trailing step: Handle the current command - if (!commands_[position_]->Execute()) + if (!commands_[position_]->Execute(jobId)) { // Error if (!permissive_) @@ -250,7 +250,7 @@ const Json::Value& source) : started_(false) { - std::auto_ptr<ICommandUnserializer> raii(unserializer); + std::unique_ptr<ICommandUnserializer> raii(unserializer); permissive_ = SerializationToolbox::ReadBoolean(source, KEY_PERMISSIVE); position_ = SerializationToolbox::ReadUnsignedInteger(source, KEY_POSITION);
--- a/Core/JobsEngine/SetOfCommandsJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/SetOfCommandsJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -49,7 +49,7 @@ { } - virtual bool Execute() = 0; + virtual bool Execute(const std::string& jobId) = 0; virtual void Serialize(Json::Value& target) const = 0; }; @@ -110,14 +110,14 @@ void SetPermissive(bool permissive); - virtual void Reset(); + virtual void Reset() ORTHANC_OVERRIDE; - virtual void Start() + virtual void Start() ORTHANC_OVERRIDE { started_ = true; } - virtual float GetProgress(); + virtual float GetProgress() ORTHANC_OVERRIDE; bool IsStarted() const { @@ -126,15 +126,15 @@ const ICommand& GetCommand(size_t index) const; - virtual JobStepResult Step(); + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE; - virtual void GetPublicContent(Json::Value& value); + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; - virtual bool Serialize(Json::Value& target); + virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE; virtual bool GetOutput(std::string& output, MimeType& mime, - const std::string& key) + const std::string& key) ORTHANC_OVERRIDE { return false; }
--- a/Core/JobsEngine/SetOfInstancesJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/SetOfInstancesJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -60,7 +60,7 @@ return instance_; } - virtual bool Execute() + virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE { if (!that_.HandleInstance(instance_)) { @@ -73,7 +73,7 @@ } } - virtual void Serialize(Json::Value& target) const + virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE { target = instance_; } @@ -91,12 +91,12 @@ { } - virtual bool Execute() + virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE { return that_.HandleTrailingStep(); } - virtual void Serialize(Json::Value& target) const + virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE { target = Json::nullValue; }
--- a/Core/JobsEngine/SetOfInstancesJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/JobsEngine/SetOfInstancesJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Logging.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Logging.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -315,6 +315,7 @@ * behavior from Google Log. *********************************************************/ +#include "Compatibility.h" #include "OrthancException.h" #include "Enumerations.h" #include "Toolbox.h" @@ -344,7 +345,7 @@ std::ostream* warning_; std::ostream* info_; - std::auto_ptr<std::ofstream> file_; + std::unique_ptr<std::ofstream> file_; LoggingContext() : infoEnabled_(false), @@ -372,7 +373,7 @@ -static std::auto_ptr<LoggingContext> loggingContext_; +static std::unique_ptr<LoggingContext> loggingContext_; static boost::mutex loggingMutex_; @@ -424,7 +425,7 @@ } - static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file, + static void PrepareLogFolder(std::unique_ptr<std::ofstream>& file, const std::string& suffix, const std::string& directory) { @@ -455,7 +456,7 @@ void Reset() { // Recover the old logging context - std::auto_ptr<LoggingContext> old; + std::unique_ptr<LoggingContext> old; { boost::mutex::scoped_lock lock(loggingMutex_); @@ -465,7 +466,11 @@ } else { - old = loggingContext_; +#if __cplusplus < 201103L + old.reset(loggingContext_.release()); +#else + old = std::move(loggingContext_); +#endif // Create a new logging context, loggingContext_.reset(new LoggingContext); @@ -512,7 +517,7 @@ if (!memento->valid_) throw std::runtime_error("Memento already used"); memento->valid_ = false; - std::auto_ptr<LoggingMementoImpl> deleter(memento); + std::unique_ptr<LoggingMementoImpl> deleter(memento); { boost::mutex::scoped_lock lock(loggingMutex_); loggingContext_.reset(new LoggingContext); @@ -553,7 +558,7 @@ } } - bool IsInfoLevelEnable() + bool IsInfoLevelEnabled() { boost::mutex::scoped_lock lock(loggingMutex_); assert(loggingContext_.get() != NULL); @@ -575,7 +580,7 @@ } } - bool IsTraceLevelEnable() + bool IsTraceLevelEnabled() { boost::mutex::scoped_lock lock(loggingMutex_); assert(loggingContext_.get() != NULL); @@ -584,7 +589,7 @@ } - static void CheckFile(std::auto_ptr<std::ofstream>& f) + static void CheckFile(std::unique_ptr<std::ofstream>& f) { if (loggingContext_->file_.get() == NULL || !loggingContext_->file_->is_open()) @@ -783,7 +788,14 @@ std::ostream* infoStream) { boost::mutex::scoped_lock lock(loggingMutex_); - std::auto_ptr<LoggingContext> old = loggingContext_; + std::unique_ptr<LoggingContext> old; + +#if __cplusplus < 201103L + old.reset(loggingContext_.release()); +#else + old = std::move(loggingContext_); +#endif + loggingContext_.reset(new LoggingContext); loggingContext_->error_ = errorStream; loggingContext_->warning_ = warningStream; @@ -797,15 +809,15 @@ FuncStreamBuf<decltype(emscripten_console_error)> globalEmscriptenErrorStreamBuf(emscripten_console_error); - std::auto_ptr<std::ostream> globalEmscriptenErrorStream; + std::unique_ptr<std::ostream> globalEmscriptenErrorStream; FuncStreamBuf<decltype(emscripten_console_warn)> globalEmscriptenWarningStreamBuf(emscripten_console_warn); - std::auto_ptr<std::ostream> globalEmscriptenWarningStream; + std::unique_ptr<std::ostream> globalEmscriptenWarningStream; FuncStreamBuf<decltype(emscripten_console_log)> globalEmscriptenInfoStreamBuf(emscripten_console_log); - std::auto_ptr<std::ostream> globalEmscriptenInfoStream; + std::unique_ptr<std::ostream> globalEmscriptenInfoStream; void EnableEmscriptenLogging() {
--- a/Core/Logging.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Logging.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaContext.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Lua/LuaContext.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaContext.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Lua/LuaContext.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaFunctionCall.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Lua/LuaFunctionCall.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaFunctionCall.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Lua/LuaFunctionCall.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/MetricsRegistry.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MetricsRegistry.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,8 +34,9 @@ #include "PrecompiledHeaders.h" #include "MetricsRegistry.h" +#include "ChunkedBuffer.h" +#include "Compatibility.h" #include "OrthancException.h" -#include "ChunkedBuffer.h" namespace Orthanc { @@ -228,7 +229,7 @@ if (found == content_.end()) { - std::auto_ptr<Item> item(new Item(type)); + std::unique_ptr<Item> item(new Item(type)); item->Update(value); content_[name] = item.release(); }
--- a/Core/MetricsRegistry.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MetricsRegistry.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/IRunnableBySteps.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MultiThreading/IRunnableBySteps.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/RunnableWorkersPool.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MultiThreading/RunnableWorkersPool.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,6 +35,7 @@ #include "RunnableWorkersPool.h" #include "SharedMessageQueue.h" +#include "../Compatibility.h" #include "../OrthancException.h" #include "../Logging.h" @@ -55,7 +56,7 @@ { try { - std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100)); + std::unique_ptr<IDynamicObject> obj(that->queue_.Dequeue(100)); if (obj.get() != NULL) { IRunnableBySteps& runnable = *dynamic_cast<IRunnableBySteps*>(obj.get());
--- a/Core/MultiThreading/RunnableWorkersPool.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MultiThreading/RunnableWorkersPool.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/Semaphore.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MultiThreading/Semaphore.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/Semaphore.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MultiThreading/Semaphore.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/SharedMessageQueue.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MultiThreading/SharedMessageQueue.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,6 +35,8 @@ #include "SharedMessageQueue.h" +#include "../Compatibility.h" + /** * FIFO (queue): @@ -137,7 +139,7 @@ } } - std::auto_ptr<IDynamicObject> message(queue_.front()); + std::unique_ptr<IDynamicObject> message(queue_.front()); queue_.pop_front(); if (queue_.empty()) @@ -199,7 +201,7 @@ { while (!queue_.empty()) { - std::auto_ptr<IDynamicObject> message(queue_.front()); + std::unique_ptr<IDynamicObject> message(queue_.front()); queue_.pop_front(); }
--- a/Core/MultiThreading/SharedMessageQueue.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/MultiThreading/SharedMessageQueue.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/OrthancException.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/OrthancException.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "Compatibility.h" #include "Enumerations.h" #include "Logging.h" @@ -53,7 +54,7 @@ HttpStatus httpStatus_; // New in Orthanc 1.5.0 - std::auto_ptr<std::string> details_; + std::unique_ptr<std::string> details_; public: OrthancException(const OrthancException& other) :
--- a/Core/Pkcs11.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Pkcs11.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -213,10 +213,11 @@ !ENGINE_set_load_privkey_function(engine, EngineLoadPrivateKey) || !ENGINE_set_RSA(engine, PKCS11_get_rsa_method()) || + +#if OPENSSL_VERSION_NUMBER < 0x10100000L // OpenSSL 1.0.2 !ENGINE_set_ECDSA(engine, PKCS11_get_ecdsa_method()) || !ENGINE_set_ECDH(engine, PKCS11_get_ecdh_method()) || - -#if OPENSSL_VERSION_NUMBER >= 0x10100002L +#else !ENGINE_set_EC(engine, PKCS11_get_ec_key_method()) || #endif
--- a/Core/Pkcs11.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Pkcs11.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/PrecompiledHeaders.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/PrecompiledHeaders.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/PrecompiledHeaders.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/PrecompiledHeaders.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -39,11 +39,11 @@ #if ORTHANC_USE_PRECOMPILED_HEADERS == 1 -#include <boost/date_time/posix_time/posix_time.hpp> -#include <boost/filesystem.hpp> +//#include <boost/date_time/posix_time/posix_time.hpp> +//#include <boost/filesystem.hpp> #include <boost/lexical_cast.hpp> -#include <boost/locale.hpp> -#include <boost/regex.hpp> +//#include <boost/locale.hpp> +//#include <boost/regex.hpp> #include <boost/thread.hpp> #include <boost/thread/shared_mutex.hpp> @@ -53,6 +53,7 @@ # include <pugixml.hpp> #endif +#include "../Core/Compatibility.h" #include "Enumerations.h" #include "Logging.h" #include "OrthancException.h"
--- a/Core/RestApi/RestApi.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApi.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApi.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApi.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #pragma once #include "RestApiHierarchy.h" +#include "../Compatibility.h" #include <list> @@ -47,7 +48,7 @@ public: static void AutoListChildren(RestApiGetCall& call); - virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target, + virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username,
--- a/Core/RestApi/RestApiCall.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiCall.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiCall.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiCall.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiDeleteCall.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiDeleteCall.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiGetCall.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiGetCall.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiGetCall.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiGetCall.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiHierarchy.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiHierarchy.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiHierarchy.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiHierarchy.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiOutput.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiOutput.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiOutput.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiOutput.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPath.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiPath.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPath.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiPath.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPostCall.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiPostCall.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPutCall.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/RestApi/RestApiPutCall.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/SQLite/FunctionContext.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SQLite/FunctionContext.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -121,7 +121,7 @@ void FunctionContext::SetStringResult(const std::string& str) { - sqlite3_result_text(context_, str.data(), str.size(), SQLITE_TRANSIENT); + sqlite3_result_text(context_, str.data(), static_cast<int>(str.size()), SQLITE_TRANSIENT); } } }
--- a/Core/SQLite/Statement.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SQLite/Statement.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -218,7 +218,7 @@ CheckOk(sqlite3_bind_text(GetStatement(), col + 1, val.data(), - val.size(), + static_cast<int>(val.size()), SQLITE_TRANSIENT), ErrorCode_BadParameterType); }
--- a/Core/SQLite/StatementReference.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SQLite/StatementReference.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -82,8 +82,12 @@ if (error != SQLITE_OK) { #if ORTHANC_SQLITE_STANDALONE != 1 - LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database) - << " (" << sqlite3_extended_errcode(database) << ")"; + int extended = sqlite3_extended_errcode(database); + LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database) << " (" << extended << ")"; + if (extended == SQLITE_IOERR_SHMSIZE /* 4874 */) + { + LOG(ERROR) << " This probably indicates that your filesystem is full"; + } #endif throw OrthancSQLiteException(ErrorCode_SQLitePrepareStatement);
--- a/Core/SerializationToolbox.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SerializationToolbox.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -320,6 +320,28 @@ } + void WriteListOfStrings(Json::Value& target, + const std::list<std::string>& values, + const std::string& field) + { + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::arrayValue; + + for (std::list<std::string>::const_iterator it = values.begin(); + it != values.end(); ++it) + { + value.append(*it); + } + } + + void WriteSetOfStrings(Json::Value& target, const std::set<std::string>& values, const std::string& field)
--- a/Core/SerializationToolbox.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SerializationToolbox.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -83,6 +83,10 @@ const std::vector<std::string>& values, const std::string& field); + void WriteListOfStrings(Json::Value& target, + const std::list<std::string>& values, + const std::string& field); + void WriteSetOfStrings(Json::Value& target, const std::set<std::string>& values, const std::string& field);
--- a/Core/SharedLibrary.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SharedLibrary.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -62,7 +62,19 @@ } #elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) - handle_ = ::dlopen(path_.c_str(), RTLD_NOW); + + /** + * "RTLD_LOCAL" is the default, and is only present to be + * explicit. "RTLD_DEEPBIND" was added in Orthanc 1.6.0, in order + * to avoid crashes while loading plugins from the LSB binaries of + * the Orthanc core. + **/ +#if defined(RTLD_DEEPBIND) // This is a GNU extension + handle_ = ::dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); +#else + handle_ = ::dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL); +#endif + if (handle_ == NULL) { std::string explanation;
--- a/Core/SharedLibrary.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SharedLibrary.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/SystemToolbox.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SystemToolbox.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/SystemToolbox.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/SystemToolbox.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/TemporaryFile.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/TemporaryFile.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/TemporaryFile.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/TemporaryFile.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Core/Toolbox.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Toolbox.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "PrecompiledHeaders.h" #include "Toolbox.h" +#include "Compatibility.h" #include "OrthancException.h" #include "Logging.h" @@ -1434,7 +1435,7 @@ #if ORTHANC_ENABLE_LOCALE == 1 - static std::auto_ptr<std::locale> globalLocale_; + static std::unique_ptr<std::locale> globalLocale_; static bool SetGlobalLocale(const char* locale) { @@ -1699,7 +1700,11 @@ #ifdef FIPS_mode_set FIPS_mode_set(0); #endif + +#if !defined(OPENSSL_NO_ENGINE) ENGINE_cleanup(); +#endif + CONF_modules_unload(1); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); @@ -2073,6 +2078,108 @@ throw OrthancException(ErrorCode_BadFileFormat, "Invalid UTF-8 string"); } } + + + std::string Toolbox::LargeHexadecimalToDecimal(const std::string& hex) + { + /** + * NB: Focus of the code below is *not* efficiency, but + * readability! + **/ + + for (size_t i = 0; i < hex.size(); i++) + { + const char c = hex[i]; + if (!((c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f') || + (c >= '0' && c <= '9'))) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "Not an hexadecimal number"); + } + } + + std::vector<uint8_t> decimal; + decimal.push_back(0); + + for (size_t i = 0; i < hex.size(); i++) + { + uint8_t hexDigit = static_cast<uint8_t>(Hex2Dec(hex[i])); + assert(hexDigit <= 15); + + for (size_t j = 0; j < decimal.size(); j++) + { + uint8_t val = static_cast<uint8_t>(decimal[j]) * 16 + hexDigit; // Maximum: 9 * 16 + 15 + assert(val <= 159 /* == 9 * 16 + 15 */); + + decimal[j] = val % 10; + hexDigit = val / 10; + assert(hexDigit <= 15 /* == 159 / 10 */); + } + + while (hexDigit > 0) + { + decimal.push_back(hexDigit % 10); + hexDigit /= 10; + } + } + + size_t start = 0; + while (start < decimal.size() && + decimal[start] == '0') + { + start++; + } + + std::string s; + s.reserve(decimal.size() - start); + + for (size_t i = decimal.size(); i > start; i--) + { + s.push_back(decimal[i - 1] + '0'); + } + + return s; + } + + + std::string Toolbox::GenerateDicomPrivateUniqueIdentifier() + { + /** + * REFERENCE: "Creating a Privately Defined Unique Identifier + * (Informative)" / "UUID Derived UID" + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html + * https://stackoverflow.com/a/46316162/881731 + **/ + + std::string uuid = GenerateUuid(); + assert(IsUuid(uuid) && uuid.size() == 36); + + /** + * After removing the four dashes ("-") out of the 36-character + * UUID, we get a large hexadecimal number with 32 characters, + * each of those characters lying in the range [0,16[. The large + * number is thus in the [0,16^32[ = [0,256^16[ range. This number + * has a maximum of 39 decimal digits, as can be seen in Python: + * + * # python -c 'import math; print(math.log(16**32))/math.log(10))' + * 38.531839445 + * + * We now to convert the large hexadecimal number to a decimal + * number with up to 39 digits, remove the leading zeros, then + * prefix it with "2.25." + **/ + + // Remove the dashes + std::string hex = (uuid.substr(0, 8) + + uuid.substr(9, 4) + + uuid.substr(14, 4) + + uuid.substr(19, 4) + + uuid.substr(24, 12)); + assert(hex.size() == 32); + + return "2.25." + LargeHexadecimalToDecimal(hex); + } }
--- a/Core/Toolbox.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/Toolbox.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -257,6 +257,11 @@ size_t& utf8Length, const std::string& utf8, size_t position); + + std::string LargeHexadecimalToDecimal(const std::string& hex); + + // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html + std::string GenerateDicomPrivateUniqueIdentifier(); } }
--- a/Core/WebServiceParameters.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/WebServiceParameters.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -304,7 +304,8 @@ break; default: - throw OrthancException(ErrorCode_BadFileFormat); + throw OrthancException(ErrorCode_BadFileFormat, + "User-defined properties associated with a Web service must be strings: " + *it); } } }
--- a/Core/WebServiceParameters.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Core/WebServiceParameters.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/INSTALL Wed Mar 18 08:59:06 2020 +0100 +++ b/INSTALL Thu Mar 19 11:48:30 2020 +0100 @@ -123,7 +123,21 @@ The option "-T host=x64" is necessary to prevent error "C1060: compiler is out of heap space" when compiling Orthanc with ICU. +Native 64-bit Windows build with Microsoft Visual Studio 2017 (msbuild) +----------------------------------------------------------------------- +# cd [...]\OrthancBuild +# cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc +Instructions to include support for Asian encodings: +# cmake -G "Visual Studio 15 2017 Win64" -T host=x64 -DSTATIC_BUILD=ON -DBOOST_LOCALE_BACKEND=icu -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc + +Native 64-bit Windows build with Microsoft Visual Studio 2019 (msbuild) +----------------------------------------------------------------------- +# cd [...]\OrthancBuild +# cmake -G "Visual Studio 16 2019" -A x64 -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc + +Instructions to include support for Asian encodings: +# cmake -G "Visual Studio 16 2019" -A x64 -T host=x64 -DSTATIC_BUILD=ON -DBOOST_LOCALE_BACKEND=icu -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc Cross-Compilation for Windows under GNU/Linux ---------------------------------------------
--- a/LinuxCompilation.txt Wed Mar 18 08:59:06 2020 +0100 +++ b/LinuxCompilation.txt Thu Mar 19 11:48:30 2020 +0100 @@ -119,10 +119,10 @@ ---------------------------- # sudo apt-get install build-essential unzip cmake mercurial \ - uuid-dev libcurl4-openssl-dev liblua5.3-0-dev \ + uuid-dev libcurl4-openssl-dev liblua5.3-dev \ libgtest-dev libpng-dev libsqlite3-dev libssl-dev libjpeg-dev \ - zlib1g-dev libdcmtk2-dev libboost-all-dev libwrap0-dev \ - libcharls-dev libjsoncpp-dev libpugixml-dev + zlib1g-dev libdcmtk-dev libboost-all-dev libwrap0-dev \ + libcharls-dev libjsoncpp-dev libpugixml-dev tzdata # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \
--- a/NEWS Wed Mar 18 08:59:06 2020 +0100 +++ b/NEWS Thu Mar 19 11:48:30 2020 +0100 @@ -2,6 +2,72 @@ =============================== +Version 1.6.0 (2020-03-18) +========================== + +General +------- + +* Support of DICOM storage commitment + +REST API +-------- + +* API version has been upgraded to 5 +* Added: + - "/peers/{id}/system": Test the connectivity with a remote peer + (and also retrieve its version number) + - "/tools/log-level": Access and/or change the log level without restarting Orthanc + - "/instances/{id}/frames/{frame}/rendered" and "/instances/{id}/rendered": + Render frames, taking windowing and resizing into account + - "/modalities/{...}/storage-commitment": Trigger storage commitment SCU + - "/storage-commitment/{...}": Access storage commitment reports + - "/storage-commitment/{...}/remove": Remove instances from storage commitment reports +* Improved: + - "/changes": Allow the "limit" argument to be greater than 100 + - "/instances": Support "Content-Encoding: gzip" to upload gzip-compressed DICOM files + - ".../modify" and "/tools/create-dicom": New option "PrivateCreator" for private tags + - "/modalities/{...}/store": New Boolean argument "StorageCommitment" + +Plugins +------- + +* New sample plugin: "ConnectivityChecks" +* New primitives to handle storage commitment SCP by plugins + +Lua +--- + +* New events: + - "OnDeletedPatient", "OnDeletedStudy", "OnDeletedSeries", "OnDeletedInstance": + triggered when a resource is deleted + - "OnUpdatedPatient", "OnUpdatedStudy", "OnUpdatedSeries", "OnUpdatedInstance": + triggered when an attachment or a metadata is updated + +Maintenance +----------- + +* New configuration options: "DefaultPrivateCreator" and "StorageCommitmentReportsSize" +* Support of MPEG4 transfer syntaxes in C-Store SCP +* C-FIND SCU at Instance level now sets the 0008,0052 tag to IMAGE per default (was INSTANCE). + Therefore, the "ClearCanvas" and "Dcm4Chee" modality manufacturer have now been deprecated. +* More strict C-FIND SCP wrt. the DICOM standard: Forbid wildcard + matching on some VRs, ignore main tags below the queried level +* Fix issue #65 (Logging improvements) +* Fix issue #103 ("queries/.../retrieve" API returns HTTP code 200 even on server errors) +* Fix issue #140 (Modifying private tags with REST API changes VR from LO to UN) +* Fix issue #154 (Matching against list of UID-s by C-MOVE) +* Fix issue #156 (Chunked Dicom-web transfer uses 100% CPU) +* Fix issue #165 (Boundary parameter in multipart Content-Type is too long) +* Fix issue #166 (CMake find_boost version is now broken with newer boost/cmake) +* Fix issue #167 (Job can't be cancelled - Handling of timeouts after established association) +* Fix issue #168 (Plugins can't read private tags from the configuration file) +* Upgraded dependencies for static builds (notably on Windows): + - dcmtk 3.6.5 + - openssl 1.1.1d + - jsoncpp 0.10.7 for pre-C++11 compilers + + Version 1.5.8 (2019-10-16) ==========================
--- a/OrthancExplorer/explorer.js Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancExplorer/explorer.js Thu Mar 19 11:48:30 2020 +0100 @@ -163,6 +163,22 @@ return d.toString('dddd, MMMM d, yyyy'); } +function FormatFloatSequence(s) +{ + if (s == undefined || s.length == 0) + return "-"; + + if (s.indexOf("\\") == -1) + return s; + + var oldValues = s.split("\\"); + var newValues = []; + for (var i = 0; i < oldValues.length; i++) + { + newValues.push(parseFloat(oldValues[i]).toFixed(3)); + } + return newValues.join("\\"); +} function Sort(arr, fieldExtractor, isInteger, reverse) { @@ -277,6 +293,11 @@ { v = SplitLongUid(v); } + else if (i == "ImagePositionPatient" || + i == "ImageOrientationPatient") + { + v = FormatFloatSequence(v); + } target.append($('<p>') .text(i + ': ') @@ -371,8 +392,7 @@ "AcquisitionNumber", "InstanceNumber", "InstanceCreationDate", - "InstanceCreationTime", - "ImagePositionPatient" + "InstanceCreationTime" ]); return CompleteFormatting(node, link, isReverse);
--- a/OrthancServer/Database/Compatibility/DatabaseLookup.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/DatabaseLookup.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/DatabaseLookup.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/DatabaseLookup.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ICreateInstance.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/ICreateInstance.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ICreateInstance.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/ICreateInstance.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/IGetChildrenMetadata.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/IGetChildrenMetadata.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResourceAndParent.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/ILookupResourceAndParent.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResourceAndParent.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/ILookupResourceAndParent.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResources.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/ILookupResources.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResources.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/ILookupResources.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ISetResourcesContent.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/ISetResourcesContent.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/SetOfResources.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/SetOfResources.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -55,7 +55,7 @@ } else { - std::auto_ptr<Resources> filtered(new Resources); + std::unique_ptr<Resources> filtered(new Resources); for (std::list<int64_t>::const_iterator it = resources.begin(); it != resources.end(); ++it) @@ -66,7 +66,11 @@ } } - resources_ = filtered; +#if __cplusplus < 201103L + resources_.reset(filtered.release()); +#else + resources_ = std::move(filtered); +#endif } } @@ -80,7 +84,7 @@ if (resources_.get() != NULL) { - std::auto_ptr<Resources> children(new Resources); + std::unique_ptr<Resources> children(new Resources); for (Resources::const_iterator it = resources_->begin(); it != resources_->end(); ++it) @@ -95,7 +99,11 @@ } } - resources_ = children; +#if __cplusplus < 201103L + resources_.reset(children.release()); +#else + resources_ = std::move(children); +#endif } switch (level_)
--- a/OrthancServer/Database/Compatibility/SetOfResources.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/Compatibility/SetOfResources.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "../../../Core/Compatibility.h" #include "../IDatabaseWrapper.h" #include "ILookupResources.h" @@ -48,9 +49,9 @@ private: typedef std::set<int64_t> Resources; - IDatabaseWrapper& database_; - ResourceType level_; - std::auto_ptr<Resources> resources_; + IDatabaseWrapper& database_; + ResourceType level_; + std::unique_ptr<Resources> resources_; public: SetOfResources(IDatabaseWrapper& database,
--- a/OrthancServer/Database/IDatabaseListener.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/IDatabaseListener.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/IDatabaseWrapper.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/IDatabaseWrapper.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/ResourcesContent.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/ResourcesContent.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/ResourcesContent.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/ResourcesContent.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/SQLiteDatabaseWrapper.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/SQLiteDatabaseWrapper.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -583,9 +583,9 @@ class SQLiteDatabaseWrapper::Transaction : public IDatabaseWrapper::ITransaction { private: - SQLiteDatabaseWrapper& that_; - std::auto_ptr<SQLite::Transaction> transaction_; - int64_t initialDiskSize_; + SQLiteDatabaseWrapper& that_; + std::unique_ptr<SQLite::Transaction> transaction_; + int64_t initialDiskSize_; public: Transaction(SQLiteDatabaseWrapper& that) : @@ -1181,7 +1181,7 @@ resourcesId.clear(); instancesId.clear(); - std::auto_ptr<SQLite::Statement> statement; + std::unique_ptr<SQLite::Statement> statement; switch (level) {
--- a/OrthancServer/Database/SQLiteDatabaseWrapper.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Database/SQLiteDatabaseWrapper.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DefaultDicomImageDecoder.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/DefaultDicomImageDecoder.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DicomInstanceOrigin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/DicomInstanceOrigin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DicomInstanceOrigin.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/DicomInstanceOrigin.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DicomInstanceToStore.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/DicomInstanceToStore.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -157,7 +157,7 @@ MetadataMap metadata_; private: - std::auto_ptr<DicomInstanceHasher> hasher_; + std::unique_ptr<DicomInstanceHasher> hasher_; void ComputeMissingInformation() {
--- a/OrthancServer/DicomInstanceToStore.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/DicomInstanceToStore.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ExportedResource.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ExportedResource.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ExportedResource.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ExportedResource.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/IDicomImageDecoder.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/IDicomImageDecoder.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/IServerListener.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/IServerListener.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/LuaScripting.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/LuaScripting.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -259,6 +259,114 @@ }; + class LuaScripting::DeleteEvent : public LuaScripting::IEvent + { + private: + ResourceType level_; + std::string publicId_; + + public: + DeleteEvent(ResourceType level, + const std::string& publicId) : + level_(level), + publicId_(publicId) + { + } + + virtual void Apply(LuaScripting& that) + { + std::string functionName; + + switch (level_) + { + case ResourceType_Patient: + functionName = "OnDeletedPatient"; + break; + + case ResourceType_Study: + functionName = "OnDeletedStudy"; + break; + + case ResourceType_Series: + functionName = "OnDeletedSeries"; + break; + + case ResourceType_Instance: + functionName = "OnDeletedInstance"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(functionName.c_str())) + { + LuaFunctionCall call(lock.GetLua(), functionName.c_str()); + call.PushString(publicId_); + call.Execute(); + } + } + } + }; + + + class LuaScripting::UpdateEvent : public LuaScripting::IEvent + { + private: + ResourceType level_; + std::string publicId_; + + public: + UpdateEvent(ResourceType level, + const std::string& publicId) : + level_(level), + publicId_(publicId) + { + } + + virtual void Apply(LuaScripting& that) + { + std::string functionName; + + switch (level_) + { + case ResourceType_Patient: + functionName = "OnUpdatedPatient"; + break; + + case ResourceType_Study: + functionName = "OnUpdatedStudy"; + break; + + case ResourceType_Series: + functionName = "OnUpdatedSeries"; + break; + + case ResourceType_Instance: + functionName = "OnUpdatedInstance"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(functionName.c_str())) + { + LuaFunctionCall call(lock.GetLua(), functionName.c_str()); + call.PushString(publicId_); + call.Execute(); + } + } + } + }; + + ServerContext* LuaScripting::GetServerContext(lua_State *state) { const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext"); @@ -502,7 +610,7 @@ if (operation == "modify") { - std::auto_ptr<DicomModification> modification(new DicomModification); + std::unique_ptr<DicomModification> modification(new DicomModification); modification->ParseModifyRequest(parameters); return lock.AddModifyInstanceOperation(context_, modification.release()); @@ -641,7 +749,7 @@ { for (;;) { - std::auto_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100)); + std::unique_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100)); if (event.get() == NULL) { @@ -738,6 +846,15 @@ { pendingEvents_.Enqueue(new StableResourceEvent(change)); } + else if (change.GetChangeType() == ChangeType_Deleted) + { + pendingEvents_.Enqueue(new DeleteEvent(change.GetResourceType(), change.GetPublicId())); + } + else if (change.GetChangeType() == ChangeType_UpdatedAttachment || + change.GetChangeType() == ChangeType_UpdatedMetadata) + { + pendingEvents_.Enqueue(new UpdateEvent(change.GetResourceType(), change.GetPublicId())); + } }
--- a/OrthancServer/LuaScripting.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/LuaScripting.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -59,6 +59,8 @@ class OnStoredInstanceEvent; class StableResourceEvent; class JobEvent; + class DeleteEvent; + class UpdateEvent; static ServerContext* GetServerContext(lua_State *state);
--- a/OrthancServer/OrthancConfiguration.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancConfiguration.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -865,4 +865,11 @@ return new TemporaryFile; } } + + + std::string OrthancConfiguration::GetDefaultPrivateCreator() const + { + // New configuration option in Orthanc 1.6.0 + return GetStringParameter("DefaultPrivateCreator", ""); + } }
--- a/OrthancServer/OrthancConfiguration.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancConfiguration.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -231,5 +231,7 @@ void ResetServerIndex(); TemporaryFile* CreateTemporaryFile() const; + + std::string GetDefaultPrivateCreator() const; }; }
--- a/OrthancServer/OrthancFindRequestHandler.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -273,7 +273,7 @@ throw OrthancException(ErrorCode_UnknownResource); // The resource was deleted in between } - std::auto_ptr<DicomMap> result(new DicomMap); + std::unique_ptr<DicomMap> result(new DicomMap); switch (level) { @@ -394,7 +394,8 @@ content.append(item); } - dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent); + dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, + "" /* no private creator */); } } @@ -536,7 +537,7 @@ const DicomMap& mainDicomTags, const Json::Value* dicomAsJson) { - std::auto_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_)); + std::unique_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_)); AddAnswer(answers_, mainDicomTags, dicomAsJson, queryAsArray_, sequencesToReturn_, counters.get());
--- a/OrthancServer/OrthancFindRequestHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancHttpHandler.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancHttpHandler.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -40,7 +40,7 @@ namespace Orthanc { bool OrthancHttpHandler::CreateChunkedRequestReader( - std::auto_ptr<IHttpHandler::IChunkedRequestReader>& target, + std::unique_ptr<IHttpHandler::IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username,
--- a/OrthancServer/OrthancHttpHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancHttpHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -50,7 +50,7 @@ { } - virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target, + virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username,
--- a/OrthancServer/OrthancInitialization.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancInitialization.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancInitialization.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancInitialization.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -59,7 +59,7 @@ RemoteModalityParameters remote_; std::string originatorAet_; uint16_t originatorId_; - std::auto_ptr<DicomUserConnection> connection_; + std::unique_ptr<DicomUserConnection> connection_; public: SynchronousMove(ServerContext& context, @@ -116,7 +116,8 @@ connection_.reset(new DicomUserConnection(localAet_, remote_)); } - connection_->Store(dicom, originatorAet_, originatorId_); + std::string sopClassUid, sopInstanceUid; // Unused + connection_->Store(sopClassUid, sopInstanceUid, dicom, originatorAet_, originatorId_); return Status_Success; } @@ -126,10 +127,10 @@ class AsynchronousMove : public IMoveRequestIterator { private: - ServerContext& context_; - std::auto_ptr<DicomModalityStoreJob> job_; - size_t position_; - size_t countInstances_; + ServerContext& context_; + std::unique_ptr<DicomModalityStoreJob> job_; + size_t position_; + size_t countInstances_; public: AsynchronousMove(ServerContext& context, @@ -142,7 +143,8 @@ position_(0) { job_->SetDescription("C-MOVE"); - job_->SetPermissive(true); + //job_->SetPermissive(true); // This was the behavior of Orthanc < 1.6.0 + job_->SetPermissive(false); job_->SetLocalAet(context.GetDefaultLocalApplicationEntityTitle()); { @@ -241,7 +243,24 @@ else { const std::string& content = value.GetContent(); - context_.GetIndex().LookupIdentifierExact(publicIds, level, tag, content); + + /** + * This tokenization fixes issue 154 ("Matching against list of + * UID-s by C-MOVE"). + * https://bitbucket.org/sjodogne/orthanc/issues/154/ + **/ + + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, content, '\\'); + for (size_t i = 0; i < tokens.size(); i++) + { + std::vector<std::string> matches; + context_.GetIndex().LookupIdentifierExact(matches, level, tag, tokens[i]); + + // Concatenate "publicIds" with "matches" + publicIds.insert(publicIds.end(), matches.begin(), matches.end()); + } + return true; } }
--- a/OrthancServer/OrthancMoveRequestHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -37,6 +37,7 @@ #include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/Logging.h" #include "../../Core/SerializationToolbox.h" +#include "../OrthancConfiguration.h" #include "../ServerContext.h" #include "../ServerJobs/MergeStudyJob.h" #include "../ServerJobs/ResourceModificationJob.h" @@ -63,6 +64,11 @@ { // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"},"Priority":9}' + { + OrthancConfiguration::ReaderLock lock; + target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator()); + } + if (call.ParseJsonRequest(request)) { target.ParseModifyRequest(request); @@ -80,6 +86,11 @@ { // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": true,"Remove":["Modality"]}' > Anonymized.dcm + { + OrthancConfiguration::ReaderLock lock; + target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator()); + } + if (call.ParseJsonRequest(request) && request.isObject()) { @@ -105,7 +116,7 @@ { std::string id = call.GetUriComponent("id", ""); - std::auto_ptr<ParsedDicomFile> modified; + std::unique_ptr<ParsedDicomFile> modified; { ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); @@ -158,7 +169,7 @@ } - static void SubmitModificationJob(std::auto_ptr<DicomModification>& modification, + static void SubmitModificationJob(std::unique_ptr<DicomModification>& modification, bool isAnonymization, RestApiPostCall& call, const Json::Value& body, @@ -166,7 +177,7 @@ { ServerContext& context = OrthancRestApi::GetContext(call); - std::auto_ptr<ResourceModificationJob> job(new ResourceModificationJob(context)); + std::unique_ptr<ResourceModificationJob> job(new ResourceModificationJob(context)); job->SetModification(modification.release(), level, isAnonymization); job->SetOrigin(call); @@ -181,7 +192,7 @@ template <enum ResourceType resourceType> static void ModifyResource(RestApiPostCall& call) { - std::auto_ptr<DicomModification> modification(new DicomModification); + std::unique_ptr<DicomModification> modification(new DicomModification); Json::Value body; ParseModifyRequest(body, *modification, call); @@ -196,7 +207,7 @@ template <enum ResourceType resourceType> static void AnonymizeResource(RestApiPostCall& call) { - std::auto_ptr<DicomModification> modification(new DicomModification); + std::unique_ptr<DicomModification> modification(new DicomModification); Json::Value body; ParseAnonymizationRequest(body, *modification, call); @@ -267,7 +278,8 @@ static void InjectTags(ParsedDicomFile& dicom, const Json::Value& tags, - bool decodeBinaryTags) + bool decodeBinaryTags, + const std::string& privateCreator) { if (tags.type() != Json::objectValue) { @@ -305,7 +317,7 @@ } else { - dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent); + dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent, privateCreator); } } } @@ -315,7 +327,8 @@ static void CreateSeries(RestApiPostCall& call, ParsedDicomFile& base /* in */, const Json::Value& content, - bool decodeBinaryTags) + bool decodeBinaryTags, + const std::string& privateCreator) { assert(content.isArray()); assert(content.size() > 0); @@ -330,7 +343,7 @@ { for (Json::ArrayIndex i = 0; i < content.size(); i++) { - std::auto_ptr<ParsedDicomFile> dicom(base.Clone(false)); + std::unique_ptr<ParsedDicomFile> dicom(base.Clone(false)); const Json::Value* payload = NULL; if (content[i].type() == Json::stringValue) @@ -348,7 +361,7 @@ if (content[i].isMember("Tags")) { - InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags); + InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator); } } @@ -538,6 +551,25 @@ decodeBinaryTags = v.asBool(); } + + // New argument in Orthanc 1.6.0 + std::string privateCreator; + if (request.isMember("PrivateCreator")) + { + const Json::Value& v = request["PrivateCreator"]; + if (v.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + privateCreator = v.asString(); + } + else + { + OrthancConfiguration::ReaderLock lock; + privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator(); + } + // Inject time-related information std::string date, time; @@ -565,7 +597,7 @@ } - InjectTags(dicom, request["Tags"], decodeBinaryTags); + InjectTags(dicom, request["Tags"], decodeBinaryTags, privateCreator); // Inject the content (either an image, or a PDF file) @@ -583,7 +615,7 @@ if (content.size() > 0) { // Let's create a series instead of a single instance - CreateSeries(call, dicom, content, decodeBinaryTags); + CreateSeries(call, dicom, content, decodeBinaryTags, privateCreator); return; } } @@ -636,7 +668,7 @@ const std::string study = call.GetUriComponent("id", ""); - std::auto_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); + std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); job->SetOrigin(call); std::vector<std::string> series; @@ -719,7 +751,7 @@ const std::string study = call.GetUriComponent("id", ""); - std::auto_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); + std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); job->SetOrigin(call); std::vector<std::string> resources;
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,11 +34,14 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../../Core/Compression/GzipCompressor.h" #include "../../Core/Logging.h" #include "../../Core/MetricsRegistry.h" #include "../../Core/SerializationToolbox.h" #include "../ServerContext.h" +#include <boost/algorithm/string/predicate.hpp> + namespace Orthanc { static void SetupResourceAnswer(Json::Value& result, @@ -118,13 +121,22 @@ "Received an empty DICOM file"); } - // TODO Remove unneccessary memcpy - std::string postData; - call.BodyToString(postData); + std::string dicom; + + if (boost::iequals(call.GetHttpHeader("content-encoding", ""), "gzip")) + { + GzipCompressor compressor; + compressor.Uncompress(dicom, call.GetBodyData(), call.GetBodySize()); + } + else + { + // TODO Remove unneccessary memcpy + call.BodyToString(dicom); + } DicomInstanceToStore toStore; toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); - toStore.SetBuffer(postData); + toStore.SetBuffer(dicom); std::string publicId; StoreStatus status = context.Store(publicId, toStore); @@ -242,7 +254,7 @@ bool synchronous, int priority) { - std::auto_ptr<IJob> raii(job); + std::unique_ptr<IJob> raii(job); if (job == NULL) { @@ -278,7 +290,7 @@ bool isDefaultSynchronous, const Json::Value& body) const { - std::auto_ptr<IJob> raii(job); + std::unique_ptr<IJob> raii(job); if (body.type() != Json::objectValue) { @@ -297,7 +309,7 @@ bool isDefaultSynchronous, const Json::Value& body) const { - std::auto_ptr<SetOfCommandsJob> raii(job); + std::unique_ptr<SetOfCommandsJob> raii(job); if (body.type() != Json::objectValue) {
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -123,7 +123,7 @@ static void SubmitJob(RestApiOutput& output, ServerContext& context, - std::auto_ptr<ArchiveJob>& job, + std::unique_ptr<ArchiveJob>& job, int priority, bool synchronous, const std::string& filename) @@ -179,7 +179,7 @@ int priority; GetJobParameters(synchronous, extended, priority, body, DEFAULT_IS_EXTENDED); - std::auto_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended)); + std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended)); AddResourcesOfInterest(*job, body); SubmitJob(call.GetOutput(), context, job, priority, synchronous, "Archive.zip"); } @@ -209,7 +209,7 @@ extended = false; } - std::auto_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended)); + std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended)); job->AddResource(id); SubmitJob(call.GetOutput(), context, job, 0 /* priority */, @@ -232,7 +232,7 @@ int priority; GetJobParameters(synchronous, extended, priority, body, DEFAULT_IS_EXTENDED); - std::auto_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended)); + std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended)); job->AddResource(id); SubmitJob(call.GetOutput(), context, job, priority, synchronous, id + ".zip"); }
--- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -45,7 +45,7 @@ bool& last, const RestApiGetCall& call) { - static const unsigned int MAX_RESULTS = 100; + static const unsigned int DEFAULT_LIMIT = 100; if (call.HasArgument("last")) { @@ -58,17 +58,14 @@ try { since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); - limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); + limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", boost::lexical_cast<std::string>(DEFAULT_LIMIT))); } catch (boost::bad_lexical_cast&) { + since = 0; + limit = DEFAULT_LIMIT; return; } - - if (limit == 0 || limit > MAX_RESULTS) - { - limit = MAX_RESULTS; - } } static void GetChanges(RestApiGetCall& call)
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -46,6 +46,7 @@ #include "../ServerJobs/DicomMoveScuJob.h" #include "../ServerJobs/OrthancPeerStoreJob.h" #include "../ServerToolbox.h" +#include "../StorageCommitmentReports.h" namespace Orthanc @@ -416,7 +417,7 @@ ***************************************************************************/ static void AnswerQueryHandler(RestApiPostCall& call, - std::auto_ptr<QueryRetrieveHandler>& handler) + std::unique_ptr<QueryRetrieveHandler>& handler) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -466,7 +467,7 @@ } else { - std::auto_ptr<QueryRetrieveHandler> handler(new QueryRetrieveHandler(context)); + std::unique_ptr<QueryRetrieveHandler> handler(new QueryRetrieveHandler(context)); handler->SetModality(call.GetUriComponent("id", "")); handler->SetLevel(StringToResourceType(request[KEY_LEVEL].asCString())); @@ -618,7 +619,7 @@ call.BodyToString(targetAet); } - std::auto_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context)); + std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context)); { QueryAccessor query(call); @@ -744,7 +745,7 @@ ServerContext& context = OrthancRestApi::GetContext(call); - std::auto_ptr<QueryRetrieveHandler> handler(new QueryRetrieveHandler(context)); + std::unique_ptr<QueryRetrieveHandler> handler(new QueryRetrieveHandler(context)); { const QueryAccessor parent(call); @@ -944,7 +945,7 @@ std::string remote = call.GetUriComponent("id", ""); Json::Value request; - std::auto_ptr<DicomModalityStoreJob> job(new DicomModalityStoreJob(context)); + std::unique_ptr<DicomModalityStoreJob> job(new DicomModalityStoreJob(context)); GetInstancesToExport(request, *job, remote, call); @@ -963,6 +964,12 @@ job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID); } + // New in Orthanc 1.6.0 + if (Toolbox::GetJsonBooleanField(request, "StorageCommitment", false)) + { + job->EnableStorageCommitment(true); + } + OrthancRestApi::GetApi(call).SubmitCommandsJob (call, job.release(), true /* synchronous by default */, request); } @@ -1084,7 +1091,7 @@ std::string remote = call.GetUriComponent("id", ""); Json::Value request; - std::auto_ptr<OrthancPeerStoreJob> job(new OrthancPeerStoreJob(context)); + std::unique_ptr<OrthancPeerStoreJob> job(new OrthancPeerStoreJob(context)); GetInstancesToExport(request, *job, remote, call); @@ -1104,6 +1111,35 @@ } } + static void PeerSystem(RestApiGetCall& call) + { + std::string remote = call.GetUriComponent("id", ""); + + OrthancConfiguration::ReaderLock lock; + + WebServiceParameters peer; + if (lock.GetConfiguration().LookupOrthancPeer(peer, remote)) + { + HttpClient client(peer, "system"); + std::string answer; + + client.SetMethod(HttpMethod_Get); + + if (!client.Apply(answer)) + { + LOG(ERROR) << "Unable to get the system info from remote Orthanc peer: " << peer.GetUrl(); + call.GetOutput().SignalError(client.GetLastStatus()); + return; + } + + call.GetOutput().AnswerBuffer(answer, MimeType_Json); + } + else + { + throw OrthancException(ErrorCode_UnknownResource, + "No peer with symbolic name: " + remote); + } + } // DICOM bridge ------------------------------------------------------------- @@ -1244,11 +1280,12 @@ if (call.ParseJsonRequest(json)) { const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = + const RemoteModalityParameters remote = MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - std::auto_ptr<ParsedDicomFile> query - (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0))); + std::unique_ptr<ParsedDicomFile> query + (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0), + "" /* no private creator */)); DicomFindAnswers answers(true); @@ -1269,6 +1306,251 @@ } + // Storage commitment SCU --------------------------------------------------- + + static void StorageCommitmentScu(RestApiPostCall& call) + { + static const char* const ORTHANC_RESOURCES = "Resources"; + static const char* const DICOM_INSTANCES = "DicomInstances"; + static const char* const SOP_CLASS_UID = "SOPClassUID"; + static const char* const SOP_INSTANCE_UID = "SOPInstanceUID"; + + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value json; + if (!call.ParseJsonRequest(json) || + json.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Must provide a JSON object with a list of resources"); + } + else if (!json.isMember(ORTHANC_RESOURCES) && + !json.isMember(DICOM_INSTANCES)) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Empty storage commitment request, one of these fields is mandatory: \"" + + std::string(ORTHANC_RESOURCES) + "\" or \"" + std::string(DICOM_INSTANCES) + "\""); + } + else + { + std::list<std::string> sopClassUids, sopInstanceUids; + + if (json.isMember(ORTHANC_RESOURCES)) + { + const Json::Value& resources = json[ORTHANC_RESOURCES]; + + if (resources.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat, + "The \"" + std::string(ORTHANC_RESOURCES) + + "\" field must provide an array of Orthanc resources"); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++) + { + if (resources[i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat, + "The \"" + std::string(ORTHANC_RESOURCES) + + "\" field must provide an array of strings, found: " + resources[i].toStyledString()); + } + + std::list<std::string> instances; + context.GetIndex().GetChildInstances(instances, resources[i].asString()); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + std::string sopClassUid, sopInstanceUid; + DicomMap tags; + if (context.LookupOrReconstructMetadata(sopClassUid, *it, MetadataType_Instance_SopClassUid) && + context.GetIndex().GetAllMainDicomTags(tags, *it) && + tags.LookupStringValue(sopInstanceUid, DICOM_TAG_SOP_INSTANCE_UID, false)) + { + sopClassUids.push_back(sopClassUid); + sopInstanceUids.push_back(sopInstanceUid); + } + else + { + throw OrthancException(ErrorCode_InternalError, + "Cannot retrieve SOP Class/Instance UID of Orthanc instance: " + *it); + } + } + } + } + } + + if (json.isMember(DICOM_INSTANCES)) + { + const Json::Value& instances = json[DICOM_INSTANCES]; + + if (instances.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat, + "The \"" + std::string(DICOM_INSTANCES) + + "\" field must provide an array of DICOM instances"); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++) + { + if (instances[i].type() == Json::arrayValue) + { + if (instances[i].size() != 2 || + instances[i][0].type() != Json::stringValue || + instances[i][1].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat, + "An instance entry must provide an array with 2 strings: " + "SOP Class UID and SOP Instance UID"); + } + else + { + sopClassUids.push_back(instances[i][0].asString()); + sopInstanceUids.push_back(instances[i][1].asString()); + } + } + else if (instances[i].type() == Json::objectValue) + { + if (!instances[i].isMember(SOP_CLASS_UID) || + !instances[i].isMember(SOP_INSTANCE_UID) || + instances[i][SOP_CLASS_UID].type() != Json::stringValue || + instances[i][SOP_INSTANCE_UID].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat, + "An instance entry must provide an object with 2 string fiels: " + "\"" + std::string(SOP_CLASS_UID) + "\" and \"" + + std::string(SOP_INSTANCE_UID)); + } + else + { + sopClassUids.push_back(instances[i][SOP_CLASS_UID].asString()); + sopInstanceUids.push_back(instances[i][SOP_INSTANCE_UID].asString()); + } + } + else + { + throw OrthancException(ErrorCode_BadFileFormat, + "JSON array or object is expected to specify one " + "instance to be queried, found: " + instances[i].toStyledString()); + } + } + } + } + + if (sopClassUids.size() != sopInstanceUids.size()) + { + throw OrthancException(ErrorCode_InternalError); + } + + const std::string transactionUid = Toolbox::GenerateDicomPrivateUniqueIdentifier(); + + if (sopClassUids.empty()) + { + LOG(WARNING) << "Issuing an outgoing storage commitment request that is empty: " << transactionUid; + } + + { + const RemoteModalityParameters remote = + MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + const std::string& remoteAet = remote.GetApplicationEntityTitle(); + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); + + // Create a "pending" storage commitment report BEFORE the + // actual SCU call in order to avoid race conditions + context.GetStorageCommitmentReports().Store( + transactionUid, new StorageCommitmentReports::Report(remoteAet)); + + DicomUserConnection scu(localAet, remote); + + std::vector<std::string> a(sopClassUids.begin(), sopClassUids.end()); + std::vector<std::string> b(sopInstanceUids.begin(), sopInstanceUids.end()); + scu.RequestStorageCommitment(transactionUid, a, b); + } + + Json::Value result = Json::objectValue; + result["ID"] = transactionUid; + result["Path"] = "/storage-commitment/" + transactionUid; + call.GetOutput().AnswerJson(result); + } + } + + + static void GetStorageCommitmentReport(RestApiGetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + const std::string& transactionUid = call.GetUriComponent("id", ""); + + { + StorageCommitmentReports::Accessor accessor( + context.GetStorageCommitmentReports(), transactionUid); + + if (accessor.IsValid()) + { + Json::Value json; + accessor.GetReport().Format(json); + call.GetOutput().AnswerJson(json); + } + else + { + throw OrthancException(ErrorCode_InexistentItem, + "No storage commitment transaction with UID: " + transactionUid); + } + } + } + + + static void RemoveAfterStorageCommitment(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + const std::string& transactionUid = call.GetUriComponent("id", ""); + + { + StorageCommitmentReports::Accessor accessor( + context.GetStorageCommitmentReports(), transactionUid); + + if (!accessor.IsValid()) + { + throw OrthancException(ErrorCode_InexistentItem, + "No storage commitment transaction with UID: " + transactionUid); + } + else if (accessor.GetReport().GetStatus() != StorageCommitmentReports::Report::Status_Success) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls, + "Cannot remove DICOM instances after failure " + "in storage commitment transaction: " + transactionUid); + } + else + { + std::vector<std::string> sopInstanceUids; + accessor.GetReport().GetSuccessSopInstanceUids(sopInstanceUids); + + for (size_t i = 0; i < sopInstanceUids.size(); i++) + { + std::vector<std::string> orthancId; + context.GetIndex().LookupIdentifierExact( + orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUids[i]); + + for (size_t j = 0; j < orthancId.size(); j++) + { + LOG(INFO) << "Storage commitment - Removing SOP instance UID / Orthanc ID: " + << sopInstanceUids[i] << " / " << orthancId[j]; + + Json::Value tmp; + context.GetIndex().DeleteResource(tmp, orthancId[j], ResourceType_Instance); + } + } + + call.GetOutput().AnswerBuffer("{}", MimeType_Json); + } + } + } + + void OrthancRestApi::RegisterModalities() { Register("/modalities", ListModalities); @@ -1309,7 +1591,13 @@ Register("/peers/{id}", UpdatePeer); Register("/peers/{id}", DeletePeer); Register("/peers/{id}/store", PeerStore); + Register("/peers/{id}/system", PeerSystem); Register("/modalities/{id}/find-worklist", DicomFindWorklist); + + // Storage commitment + Register("/modalities/{id}/storage-commitment", StorageCommitmentScu); + Register("/storage-commitment/{id}", GetStorageCommitmentReport); + Register("/storage-commitment/{id}/remove", RemoveAfterStorageCommitment); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,10 +35,13 @@ #include "OrthancRestApi.h" #include "../../Core/Compression/GzipCompressor.h" +#include "../../Core/DicomFormat/DicomImageInformation.h" #include "../../Core/DicomParsing/DicomWebJsonVisitor.h" #include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" #include "../../Core/HttpServer/HttpContentNegociation.h" +#include "../../Core/Images/Image.h" +#include "../../Core/Images/ImageProcessing.h" #include "../../Core/Logging.h" #include "../DefaultDicomImageDecoder.h" #include "../OrthancConfiguration.h" @@ -49,6 +52,9 @@ #include "../../Plugins/Engine/OrthancPlugins.h" +// This "include" is mandatory for Release builds using Linux Standard Base +#include <boost/math/special_functions/round.hpp> + namespace Orthanc { @@ -382,14 +388,14 @@ class ImageToEncode { private: - std::auto_ptr<ImageAccessor>& image_; + std::unique_ptr<ImageAccessor>& image_; ImageExtractionMode mode_; bool invert_; MimeType format_; std::string answer_; public: - ImageToEncode(std::auto_ptr<ImageAccessor>& image, + ImageToEncode(std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert) : image_(image), @@ -502,113 +508,445 @@ } - template <enum ImageExtractionMode mode> - static void GetImage(RestApiGetCall& call) + namespace { - ServerContext& context = OrthancRestApi::GetContext(call); - - std::string frameId = call.GetUriComponent("frame", "0"); - - unsigned int frame; - try + class IDecodedFrameHandler : public boost::noncopyable { - frame = boost::lexical_cast<unsigned int>(frameId); - } - catch (boost::bad_lexical_cast&) - { - return; - } + public: + virtual ~IDecodedFrameHandler() + { + } + + virtual void Handle(RestApiGetCall& call, + std::unique_ptr<ImageAccessor>& decoded, + const DicomMap& dicom) = 0; + + virtual bool RequiresDicomTags() const = 0; + + static void Apply(RestApiGetCall& call, + IDecodedFrameHandler& handler) + { + ServerContext& context = OrthancRestApi::GetContext(call); - bool invert = false; - std::auto_ptr<ImageAccessor> decoded; + std::string frameId = call.GetUriComponent("frame", "0"); - try - { - std::string publicId = call.GetUriComponent("id", ""); + unsigned int frame; + try + { + frame = boost::lexical_cast<unsigned int>(frameId); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + DicomMap dicom; + std::unique_ptr<ImageAccessor> decoded; + + try + { + std::string publicId = call.GetUriComponent("id", ""); #if ORTHANC_ENABLE_PLUGINS == 1 - if (context.GetPlugins().HasCustomImageDecoder()) + if (context.GetPlugins().HasCustomImageDecoder()) + { + // TODO create a cache of file + std::string dicomContent; + context.ReadDicom(dicomContent, publicId); + decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame)); + + /** + * Note that we call "DecodeUnsafe()": We do not fallback to + * the builtin decoder if no installed decoder plugin is able + * to decode the image. This allows us to take advantage of + * the cache below. + **/ + + if (handler.RequiresDicomTags() && + decoded.get() != NULL) + { + // TODO Optimize this lookup for photometric interpretation: + // It should be implemented by the plugin to avoid parsing + // twice the DICOM file + ParsedDicomFile parsed(dicomContent); + parsed.ExtractDicomSummary(dicom); + } + } +#endif + + if (decoded.get() == NULL) + { + // Use Orthanc's built-in decoder, using the cache to speed-up + // things on multi-frame images + ServerContext::DicomCacheLocker locker(context, publicId); + decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame)); + + if (handler.RequiresDicomTags()) + { + locker.GetDicom().ExtractDicomSummary(dicom); + } + } + } + catch (OrthancException& e) + { + if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || + e.GetErrorCode() == ErrorCode_UnknownResource) + { + // The frame number is out of the range for this DICOM + // instance, the resource is not existent + } + else + { + std::string root = ""; + for (size_t i = 1; i < call.GetFullUri().size(); i++) + { + root += "../"; + } + + call.GetOutput().Redirect(root + "app/images/unsupported.png"); + } + return; + } + + handler.Handle(call, decoded, dicom); + } + + + static void DefaultHandler(RestApiGetCall& call, + std::unique_ptr<ImageAccessor>& decoded, + ImageExtractionMode mode, + bool invert) { - // TODO create a cache of file - std::string dicomContent; - context.ReadDicom(dicomContent, publicId); - decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame)); + ImageToEncode image(decoded, mode, invert); + + HttpContentNegociation negociation; + EncodePng png(image); + negociation.Register(MIME_PNG, png); + + EncodeJpeg jpeg(image, call); + negociation.Register(MIME_JPEG, jpeg); + + EncodePam pam(image); + negociation.Register(MIME_PAM, pam); + + if (negociation.Apply(call.GetHttpHeaders())) + { + image.Answer(call.GetOutput()); + } + } + }; + + + class GetImageHandler : public IDecodedFrameHandler + { + private: + ImageExtractionMode mode_; + + public: + GetImageHandler(ImageExtractionMode mode) : + mode_(mode) + { + } + + virtual void Handle(RestApiGetCall& call, + std::unique_ptr<ImageAccessor>& decoded, + const DicomMap& dicom) ORTHANC_OVERRIDE + { + bool invert = false; + + if (mode_ == ImageExtractionMode_Preview) + { + DicomImageInformation info(dicom); + invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); + } + + DefaultHandler(call, decoded, mode_, invert); + } + + virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE + { + return mode_ == ImageExtractionMode_Preview; + } + }; + + + class RenderedFrameHandler : public IDecodedFrameHandler + { + private: + static void GetDicomParameters(bool& invert, + float& rescaleSlope, + float& rescaleIntercept, + float& windowWidth, + float& windowCenter, + const DicomMap& dicom) + { + DicomImageInformation info(dicom); + + invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); + + rescaleSlope = 1.0f; + rescaleIntercept = 0.0f; + + if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && + dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) + { + dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE); + dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); + } + + windowWidth = static_cast<float>(1 << info.GetBitsStored()); + windowCenter = windowWidth / 2.0f; + + if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && + dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) + { + dicom.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); + dicom.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); + } + } + + static void GetUserArguments(float& windowWidth /* inout */, + float& windowCenter /* inout */, + unsigned int& argWidth, + unsigned int& argHeight, + bool& smooth, + RestApiGetCall& call) + { + static const char* ARG_WINDOW_CENTER = "window-center"; + static const char* ARG_WINDOW_WIDTH = "window-width"; + static const char* ARG_WIDTH = "width"; + static const char* ARG_HEIGHT = "height"; + static const char* ARG_SMOOTH = "smooth"; + + if (call.HasArgument(ARG_WINDOW_WIDTH)) + { + try + { + windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, "")); + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH)); + } + } - /** - * Note that we call "DecodeUnsafe()": We do not fallback to - * the builtin decoder if no installed decoder plugin is able - * to decode the image. This allows us to take advantage of - * the cache below. - **/ + if (call.HasArgument(ARG_WINDOW_CENTER)) + { + try + { + windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, "")); + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Bad value for argument: " + std::string(ARG_WINDOW_CENTER)); + } + } + + argWidth = 0; + argHeight = 0; - if (mode == ImageExtractionMode_Preview && - decoded.get() != NULL) + if (call.HasArgument(ARG_WIDTH)) + { + try + { + int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_WIDTH, "")); + if (tmp < 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Argument cannot be negative: " + std::string(ARG_WIDTH)); + } + else + { + argWidth = static_cast<unsigned int>(tmp); + } + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Bad value for argument: " + std::string(ARG_WIDTH)); + } + } + + if (call.HasArgument(ARG_HEIGHT)) + { + try + { + int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_HEIGHT, "")); + if (tmp < 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Argument cannot be negative: " + std::string(ARG_HEIGHT)); + } + else + { + argHeight = static_cast<unsigned int>(tmp); + } + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Bad value for argument: " + std::string(ARG_HEIGHT)); + } + } + + smooth = false; + + if (call.HasArgument(ARG_SMOOTH)) { - // TODO Optimize this lookup for photometric interpretation: - // It should be implemented by the plugin to avoid parsing - // twice the DICOM file - ParsedDicomFile parsed(dicomContent); + std::string value = call.GetArgument(ARG_SMOOTH, ""); + if (value == "0" || + value == "false") + { + smooth = false; + } + else if (value == "1" || + value == "true") + { + smooth = true; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Argument must be Boolean: " + std::string(ARG_SMOOTH)); + } + } + } + + + public: + virtual void Handle(RestApiGetCall& call, + std::unique_ptr<ImageAccessor>& decoded, + const DicomMap& dicom) ORTHANC_OVERRIDE + { + bool invert; + float rescaleSlope, rescaleIntercept, windowWidth, windowCenter; + GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, dicom); + + unsigned int argWidth, argHeight; + bool smooth; + GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call); + + unsigned int targetWidth = decoded->GetWidth(); + unsigned int targetHeight = decoded->GetHeight(); + + if (decoded->GetWidth() != 0 && + decoded->GetHeight() != 0) + { + float ratio = 1; + + if (argWidth != 0 && + argHeight != 0) + { + float ratioX = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); + float ratioY = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); + ratio = std::min(ratioX, ratioY); + } + else if (argWidth != 0) + { + ratio = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); + } + else if (argHeight != 0) + { + ratio = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); + } - PhotometricInterpretation photometric; - if (parsed.LookupPhotometricInterpretation(photometric)) + targetWidth = boost::math::iround(ratio * static_cast<float>(decoded->GetWidth())); + targetHeight = boost::math::iround(ratio * static_cast<float>(decoded->GetHeight())); + } + + if (decoded->GetFormat() == PixelFormat_RGB24) + { + if (targetWidth == decoded->GetWidth() && + targetHeight == decoded->GetHeight()) + { + DefaultHandler(call, decoded, ImageExtractionMode_Preview, false); + } + else { - invert = (photometric == PhotometricInterpretation_Monochrome1); + std::unique_ptr<ImageAccessor> resized( + new Image(decoded->GetFormat(), targetWidth, targetHeight, false)); + + if (smooth && + (targetWidth < decoded->GetWidth() || + targetHeight < decoded->GetHeight())) + { + ImageProcessing::SmoothGaussian5x5(*decoded); + } + + ImageProcessing::Resize(*resized, *decoded); + DefaultHandler(call, resized, ImageExtractionMode_Preview, false); + } + } + else + { + // Grayscale image: (1) convert to Float32, (2) apply + // windowing to get a Grayscale8, (3) possibly resize + + Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false); + ImageProcessing::Convert(converted, *decoded); + + // Avoid divisions by zero + if (windowWidth <= 1.0f) + { + windowWidth = 1; + } + + if (std::abs(rescaleSlope) <= 0.1f) + { + rescaleSlope = 0.1f; + } + + const float scaling = 255.0f * rescaleSlope / windowWidth; + const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope; + + std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false)); + ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false); + + if (targetWidth == decoded->GetWidth() && + targetHeight == decoded->GetHeight()) + { + DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert); + } + else + { + std::unique_ptr<ImageAccessor> resized( + new Image(PixelFormat_Grayscale8, targetWidth, targetHeight, false)); + + if (smooth && + (targetWidth < decoded->GetWidth() || + targetHeight < decoded->GetHeight())) + { + ImageProcessing::SmoothGaussian5x5(*rescaled); + } + + ImageProcessing::Resize(*resized, *rescaled); + DefaultHandler(call, resized, ImageExtractionMode_UInt8, invert); } } } -#endif - if (decoded.get() == NULL) - { - // Use Orthanc's built-in decoder, using the cache to speed-up - // things on multi-frame images - ServerContext::DicomCacheLocker locker(context, publicId); - decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame)); - - PhotometricInterpretation photometric; - if (mode == ImageExtractionMode_Preview && - locker.GetDicom().LookupPhotometricInterpretation(photometric)) - { - invert = (photometric == PhotometricInterpretation_Monochrome1); - } - } - } - catch (OrthancException& e) - { - if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || e.GetErrorCode() == ErrorCode_UnknownResource) - { - // The frame number is out of the range for this DICOM - // instance, the resource is not existent - } - else + virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE { - std::string root = ""; - for (size_t i = 1; i < call.GetFullUri().size(); i++) - { - root += "../"; - } + return true; + } + }; + } - call.GetOutput().Redirect(root + "app/images/unsupported.png"); - } - return; - } - - ImageToEncode image(decoded, mode, invert); - HttpContentNegociation negociation; - EncodePng png(image); - negociation.Register(MIME_PNG, png); - - EncodeJpeg jpeg(image, call); - negociation.Register(MIME_JPEG, jpeg); + template <enum ImageExtractionMode mode> + static void GetImage(RestApiGetCall& call) + { + GetImageHandler handler(mode); + IDecodedFrameHandler::Apply(call, handler); + } - EncodePam pam(image); - negociation.Register(MIME_PAM, pam); - if (negociation.Apply(call.GetHttpHeaders())) - { - image.Answer(call.GetOutput()); - } + static void GetRenderedFrame(RestApiGetCall& call) + { + RenderedFrameHandler handler; + IDecodedFrameHandler::Apply(call, handler); } @@ -638,7 +976,7 @@ DefaultDicomImageDecoder decoder; // This is Orthanc's built-in decoder #endif - std::auto_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame)); + std::unique_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame)); std::string result; decoded->ToMatlabString(result); @@ -1762,6 +2100,7 @@ Register("/instances/{id}/frames", ListFrames); Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/frames/{frame}/rendered", GetRenderedFrame); 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>); @@ -1770,6 +2109,7 @@ Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); Register("/instances/{id}/pdf", ExtractPdf); Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/rendered", GetRenderedFrame); Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>);
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -42,6 +42,11 @@ #include "../ServerContext.h" +static const char* LOG_LEVEL_DEFAULT = "default"; +static const char* LOG_LEVEL_VERBOSE = "verbose"; +static const char* LOG_LEVEL_TRACE = "trace"; + + namespace Orthanc { // System information ------------------------------------------------------- @@ -490,6 +495,62 @@ } + static void GetLogLevel(RestApiGetCall& call) + { + std::string s; + + if (Logging::IsTraceLevelEnabled()) + { + s = LOG_LEVEL_TRACE; + } + else if (Logging::IsInfoLevelEnabled()) + { + s = LOG_LEVEL_VERBOSE; + } + else + { + s = LOG_LEVEL_DEFAULT; + } + + call.GetOutput().AnswerBuffer(s, MimeType_PlainText); + } + + + static void PutLogLevel(RestApiPutCall& call) + { + std::string body; + call.BodyToString(body); + + if (body == LOG_LEVEL_DEFAULT) + { + Logging::EnableInfoLevel(false); + Logging::EnableTraceLevel(false); + } + else if (body == LOG_LEVEL_VERBOSE) + { + Logging::EnableInfoLevel(true); + Logging::EnableTraceLevel(false); + } + else if (body == LOG_LEVEL_TRACE) + { + Logging::EnableInfoLevel(true); + Logging::EnableTraceLevel(true); + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "The log level must be one of the following values: \"" + + std::string(LOG_LEVEL_DEFAULT) + "\", \"" + + std::string(LOG_LEVEL_VERBOSE) + "\", of \"" + + std::string(LOG_LEVEL_TRACE) + "\""); + } + + // Success + LOG(WARNING) << "REST API call has switched the log level to: " << body; + call.GetOutput().AnswerBuffer("", MimeType_PlainText); + } + + void OrthancRestApi::RegisterSystem() { Register("/", ServeRoot); @@ -505,6 +566,8 @@ Register("/tools/metrics", GetMetricsEnabled); Register("/tools/metrics", PutMetricsEnabled); Register("/tools/metrics-prometheus", GetMetricsPrometheus); + Register("/tools/log-level", GetLogLevel); + Register("/tools/log-level", PutLogLevel); Register("/plugins", ListPlugins); Register("/plugins/{id}", GetPlugin);
--- a/OrthancServer/PrecompiledHeadersServer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/PrecompiledHeadersServer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/PrecompiledHeadersServer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/PrecompiledHeadersServer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/QueryRetrieveHandler.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/QueryRetrieveHandler.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/QueryRetrieveHandler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/QueryRetrieveHandler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DatabaseConstraint.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/DatabaseConstraint.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DatabaseConstraint.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/DatabaseConstraint.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DatabaseLookup.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/DatabaseLookup.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -117,9 +117,9 @@ } std::set<DicomTag> ignoreTagLength; - std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement - (*element, DicomToJsonFlags_None, - 0, encoding, hasCodeExtensions, ignoreTagLength)); + std::unique_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_None, + 0, encoding, hasCodeExtensions, ignoreTagLength)); // WARNING: Also modify "HierarchicalMatcher::Setup()" if modifying this code if (value.get() == NULL || @@ -185,7 +185,7 @@ fixedTag = DICOM_TAG_MODALITY; } - std::auto_ptr<DicomTagConstraint> constraint + std::unique_ptr<DicomTagConstraint> constraint (new DicomTagConstraint(fixedTag, ConstraintType_List, caseSensitive, mandatoryTag)); std::vector<std::string> items; @@ -198,8 +198,26 @@ AddConstraint(constraint.release()); } - else if (dicomQuery.find('*') != std::string::npos || - dicomQuery.find('?') != std::string::npos) + else if ( + /** + * New test in Orthanc 1.6.0: Wild card matching is only allowed + * for a subset of value representations: AE, CS, LO, LT, PN, + * SH, ST, UC, UR, UT. + * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part04/sect_C.2.2.2.4.html + **/ + (vr == ValueRepresentation_ApplicationEntity || // AE + vr == ValueRepresentation_CodeString || // CS + vr == ValueRepresentation_LongString || // LO + vr == ValueRepresentation_LongText || // LT + vr == ValueRepresentation_PersonName || // PN + vr == ValueRepresentation_ShortString || // SH + vr == ValueRepresentation_ShortText || // ST + vr == ValueRepresentation_UnlimitedCharacters || // UC + vr == ValueRepresentation_UniversalResource || // UR + vr == ValueRepresentation_UnlimitedText // UT + ) && + (dicomQuery.find('*') != std::string::npos || + dicomQuery.find('?') != std::string::npos)) { AddConstraint(new DicomTagConstraint (tag, ConstraintType_Wildcard, dicomQuery, caseSensitive, mandatoryTag));
--- a/OrthancServer/Search/DatabaseLookup.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/DatabaseLookup.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DicomTagConstraint.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/DicomTagConstraint.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DicomTagConstraint.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/DicomTagConstraint.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/HierarchicalMatcher.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/HierarchicalMatcher.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -123,9 +123,9 @@ flatTags_.insert(tag); std::set<DicomTag> ignoreTagLength; - std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement - (*element, DicomToJsonFlags_None, - 0, encoding, hasCodeExtensions, ignoreTagLength)); + std::unique_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_None, + 0, encoding, hasCodeExtensions, ignoreTagLength)); // WARNING: Also modify "DatabaseLookup::IsMatch()" if modifying this code if (value.get() == NULL || @@ -257,7 +257,7 @@ Encoding encoding, bool hasCodeExtensions) const { - std::auto_ptr<DcmDataset> target(new DcmDataset); + std::unique_ptr<DcmDataset> target(new DcmDataset); for (std::set<DicomTag>::const_iterator it = flatTags_.begin(); it != flatTags_.end(); ++it) @@ -268,7 +268,13 @@ if (source.findAndGetElement(tag, element).good() && element != NULL) { - std::auto_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(*it)); + if (it->IsPrivate()) + { + throw OrthancException(ErrorCode_NotImplemented, + "Not applicable to private tags: " + it->Format()); + } + + std::unique_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(*it, "" /* no private creator */)); cloned->copyFrom(*element); target->insert(cloned.release()); } @@ -283,7 +289,7 @@ if (source.findAndGetSequence(tag, sequence).good() && sequence != NULL) { - std::auto_ptr<DcmSequenceOfItems> cloned(new DcmSequenceOfItems(tag)); + std::unique_ptr<DcmSequenceOfItems> cloned(new DcmSequenceOfItems(tag)); for (unsigned long i = 0; i < sequence->card(); i++) { @@ -297,7 +303,7 @@ // "DcmItem" object before it can be included in a // sequence. Otherwise, "dciodvfy" reports an error "Bad // tag in sequence - Expecting Item or Sequence Delimiter." - std::auto_ptr<DcmDataset> child(it->second->ExtractInternal(*sequence->getItem(i), encoding, hasCodeExtensions)); + std::unique_ptr<DcmDataset> child(it->second->ExtractInternal(*sequence->getItem(i), encoding, hasCodeExtensions)); cloned->append(new DcmItem(*child)); } } @@ -315,10 +321,10 @@ bool hasCodeExtensions; Encoding encoding = dicom.DetectEncoding(hasCodeExtensions); - std::auto_ptr<DcmDataset> dataset(ExtractInternal(*dicom.GetDcmtkObject().getDataset(), - encoding, hasCodeExtensions)); + std::unique_ptr<DcmDataset> dataset(ExtractInternal(*dicom.GetDcmtkObject().getDataset(), + encoding, hasCodeExtensions)); - std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(*dataset)); + std::unique_ptr<ParsedDicomFile> result(new ParsedDicomFile(*dataset)); result->SetEncoding(encoding); return result.release();
--- a/OrthancServer/Search/HierarchicalMatcher.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/HierarchicalMatcher.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/ISqlLookupFormatter.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/ISqlLookupFormatter.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/ISqlLookupFormatter.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/Search/ISqlLookupFormatter.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerContext.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerContext.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -49,6 +49,7 @@ #include "Search/DatabaseLookup.h" #include "ServerJobs/OrthancJobUnserializer.h" #include "ServerToolbox.h" +#include "StorageCommitmentReports.h" #include <EmbeddedResources.h> #include <dcmtk/dcmdata/dcfilefo.h> @@ -75,7 +76,7 @@ { while (!that->done_) { - std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay)); + std::unique_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay)); if (obj.get() != NULL) { @@ -254,6 +255,14 @@ jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2)); saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true); metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true)); + + // New configuration options in Orthanc 1.5.1 + findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always")); + limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0); + limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0); + + // New configuration option in Orthanc 1.6.0 + storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100))); } jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); @@ -659,7 +668,7 @@ lock_(that_.dicomCacheMutex_) { #if ENABLE_DICOM_CACHE == 0 - static std::auto_ptr<IDynamicObject> p; + static std::unique_ptr<IDynamicObject> p; p.reset(provider_.Provide(instancePublicId)); dicom_ = dynamic_cast<ParsedDicomFile*>(p.get()); #else @@ -796,44 +805,9 @@ size_t since, size_t limit) { - LookupMode mode; - unsigned int databaseLimit; + unsigned int databaseLimit = (queryLevel == ResourceType_Instance ? + limitFindInstances_ : limitFindResults_); - { - // New configuration option in 1.5.1 - OrthancConfiguration::ReaderLock lock; - - std::string value = lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"); - - if (value == "Always") - { - mode = LookupMode_DiskOnLookupAndAnswer; - } - else if (value == "Never") - { - mode = LookupMode_DatabaseOnly; - } - else if (value == "Answers") - { - mode = LookupMode_DiskOnAnswer; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Configuration option \"StorageAccessOnFind\" " - "should be \"Always\", \"Never\" or \"Answers\": " + value); - } - - if (queryLevel == ResourceType_Instance) - { - databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0); - } - else - { - databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0); - } - } - std::vector<std::string> resources, instances; { @@ -846,6 +820,11 @@ LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size(); + /** + * "resources" contains the Orthanc ID of the resource at level + * "queryLevel", "instances" contains one the Orthanc ID of one + * sample instance from this resource. + **/ assert(resources.size() == instances.size()); size_t countResults = 0; @@ -858,24 +837,59 @@ // Optimization in Orthanc 1.5.1 - Don't read the full JSON from // the disk if only "main DICOM tags" are to be returned - std::auto_ptr<Json::Value> dicomAsJson; + std::unique_ptr<Json::Value> dicomAsJson; bool hasOnlyMainDicomTags; DicomMap dicom; - if (mode == LookupMode_DatabaseOnly || - mode == LookupMode_DiskOnAnswer || + if (findStorageAccessMode_ == FindStorageAccessMode_DatabaseOnly || + findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer || lookup.HasOnlyMainDicomTags()) { // Case (1): The main DICOM tags, as stored in the database, // are sufficient to look for match - if (!GetIndex().GetAllMainDicomTags(dicom, instances[i])) + DicomMap tmp; + if (!GetIndex().GetAllMainDicomTags(tmp, instances[i])) { // The instance has been removed during the execution of the // lookup, ignore it continue; } + +#if 1 + // New in Orthanc 1.6.0: Only keep the main DICOM tags at the + // level of interest for the query + switch (queryLevel) + { + // WARNING: Don't reorder cases below, and don't add "break" + case ResourceType_Instance: + dicom.MergeMainDicomTags(tmp, ResourceType_Instance); + + case ResourceType_Series: + dicom.MergeMainDicomTags(tmp, ResourceType_Series); + + case ResourceType_Study: + dicom.MergeMainDicomTags(tmp, ResourceType_Study); + + case ResourceType_Patient: + dicom.MergeMainDicomTags(tmp, ResourceType_Patient); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + // Special case of the "Modality" at the study level, in order + // to deal with C-FIND on "ModalitiesInStudy" (0008,0061). + // Check out integration test "test_rest_modalities_in_study". + if (queryLevel == ResourceType_Study) + { + dicom.CopyTagIfExists(tmp, DICOM_TAG_MODALITY); + } +#else + dicom.Assign(tmp); // This emulates Orthanc <= 1.5.8 +#endif hasOnlyMainDicomTags = true; } @@ -907,8 +921,8 @@ } else { - if ((mode == LookupMode_DiskOnLookupAndAnswer || - mode == LookupMode_DiskOnAnswer) && + if ((findStorageAccessMode_ == FindStorageAccessMode_DiskOnLookupAndAnswer || + findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer) && dicomAsJson.get() == NULL && isDicomAsJsonNeeded) { @@ -1052,4 +1066,24 @@ } #endif } + + + IStorageCommitmentFactory::ILookupHandler* + ServerContext::CreateStorageCommitment(const std::string& jobId, + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) + { +#if ORTHANC_ENABLE_PLUGINS == 1 + if (HasPlugins()) + { + return GetPlugins().CreateStorageCommitment( + jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet); + } +#endif + + return NULL; + } }
--- a/OrthancServer/ServerContext.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerContext.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -37,6 +37,7 @@ #include "LuaScripting.h" #include "OrthancHttpHandler.h" #include "ServerIndex.h" +#include "ServerJobs/IStorageCommitmentFactory.h" #include "../Core/Cache/MemoryCache.h" @@ -53,6 +54,7 @@ class SetOfInstancesJob; class SharedArchive; class SharedMessageQueue; + class StorageCommitmentReports; /** @@ -60,7 +62,9 @@ * filesystem (including compression), as well as the index of the * DICOM store. It implements the required locking mechanisms. **/ - class ServerContext : private JobsRegistry::IObserver + class ServerContext : + public IStorageCommitmentFactory, + private JobsRegistry::IObserver { public: class ILookupVisitor : public boost::noncopyable @@ -82,14 +86,6 @@ private: - enum LookupMode - { - LookupMode_DatabaseOnly, - LookupMode_DiskOnAnswer, - LookupMode_DiskOnLookupAndAnswer - }; - - class LuaServerListener : public IServerListener { private: @@ -120,7 +116,7 @@ } }; - class DicomCacheProvider : public ICachePageProvider + class DicomCacheProvider : public Deprecated::ICachePageProvider // TODO { private: ServerContext& context_; @@ -172,11 +168,11 @@ void SaveJobsEngine(); - virtual void SignalJobSubmitted(const std::string& jobId); + virtual void SignalJobSubmitted(const std::string& jobId) ORTHANC_OVERRIDE; - virtual void SignalJobSuccess(const std::string& jobId); + virtual void SignalJobSuccess(const std::string& jobId) ORTHANC_OVERRIDE; - virtual void SignalJobFailure(const std::string& jobId); + virtual void SignalJobFailure(const std::string& jobId) ORTHANC_OVERRIDE; ServerIndex index_; IStorageArea& area_; @@ -186,12 +182,12 @@ DicomCacheProvider provider_; boost::mutex dicomCacheMutex_; - MemoryCache dicomCache_; + Deprecated::MemoryCache dicomCache_; // TODO LuaScripting mainLua_; LuaScripting filterLua_; LuaServerListener luaListener_; - std::auto_ptr<SharedArchive> mediaArchive_; + std::unique_ptr<SharedArchive> mediaArchive_; // The "JobsEngine" must be *after* "LuaScripting", as // "LuaScripting" embeds "LuaJobManager" that registers as an @@ -214,15 +210,20 @@ boost::thread changeThread_; boost::thread saveJobsThread_; - std::auto_ptr<SharedArchive> queryRetrieveArchive_; + std::unique_ptr<SharedArchive> queryRetrieveArchive_; std::string defaultLocalAet_; OrthancHttpHandler httpHandler_; bool saveJobs_; + FindStorageAccessMode findStorageAccessMode_; + unsigned int limitFindInstances_; + unsigned int limitFindResults_; - std::auto_ptr<MetricsRegistry> metricsRegistry_; + std::unique_ptr<MetricsRegistry> metricsRegistry_; bool isHttpServerSecure_; bool isExecuteLuaEnabled_; + std::unique_ptr<StorageCommitmentReports> storageCommitmentReports_; + public: class DicomCacheLocker : public boost::noncopyable { @@ -424,5 +425,18 @@ { return isExecuteLuaEnabled_; } + + virtual IStorageCommitmentFactory::ILookupHandler* + CreateStorageCommitment(const std::string& jobId, + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE; + + StorageCommitmentReports& GetStorageCommitmentReports() + { + return *storageCommitmentReports_; + } }; }
--- a/OrthancServer/ServerEnumerations.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerEnumerations.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -192,6 +192,30 @@ return dictContentType_.Translate(str); } + + FindStorageAccessMode StringToFindStorageAccessMode(const std::string& value) + { + if (value == "Always") + { + return FindStorageAccessMode_DiskOnLookupAndAnswer; + } + else if (value == "Never") + { + return FindStorageAccessMode_DatabaseOnly; + } + else if (value == "Answers") + { + return FindStorageAccessMode_DiskOnAnswer; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Configuration option \"StorageAccessOnFind\" " + "should be \"Always\", \"Never\" or \"Answers\": " + value); + } + } + + std::string GetBasePath(ResourceType type, const std::string& publicId) {
--- a/OrthancServer/ServerEnumerations.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerEnumerations.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -83,6 +83,13 @@ }; } + enum FindStorageAccessMode + { + FindStorageAccessMode_DatabaseOnly, + FindStorageAccessMode_DiskOnAnswer, + FindStorageAccessMode_DiskOnLookupAndAnswer + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -178,6 +185,8 @@ FileContentType StringToContentType(const std::string& str); + FindStorageAccessMode StringToFindStorageAccessMode(const std::string& str); + std::string EnumerationToString(FileContentType type); std::string GetFileContentMime(FileContentType type);
--- a/OrthancServer/ServerIndex.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerIndex.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -152,7 +152,15 @@ it = pendingFilesToRemove_.begin(); it != pendingFilesToRemove_.end(); ++it) { - context_.RemoveFile(it->GetUuid(), it->GetContentType()); + try + { + context_.RemoveFile(it->GetUuid(), it->GetContentType()); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to remove an attachment from the storage area: " + << it->GetUuid() << " (type: " << EnumerationToString(it->GetContentType()) << ")"; + } } } @@ -233,7 +241,7 @@ { private: ServerIndex& index_; - std::auto_ptr<IDatabaseWrapper::ITransaction> transaction_; + std::unique_ptr<IDatabaseWrapper::ITransaction> transaction_; bool isCommitted_; public: @@ -356,34 +364,40 @@ void LoadTags(ResourceType level) { - const DicomTag* tags = NULL; - size_t size; + { + const DicomTag* tags = NULL; + size_t size; - ServerToolbox::LoadIdentifiers(tags, size, level); + ServerToolbox::LoadIdentifiers(tags, size, level); - for (size_t i = 0; i < size; i++) - { - if (registry_.find(tags[i]) == registry_.end()) + for (size_t i = 0; i < size; i++) { - registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier); - } - else - { - // These patient-level tags are copied in the study level - assert(level == ResourceType_Study && - (tags[i] == DICOM_TAG_PATIENT_ID || - tags[i] == DICOM_TAG_PATIENT_NAME || - tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE)); + if (registry_.find(tags[i]) == registry_.end()) + { + registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier); + } + else + { + // These patient-level tags are copied in the study level + assert(level == ResourceType_Study && + (tags[i] == DICOM_TAG_PATIENT_ID || + tags[i] == DICOM_TAG_PATIENT_NAME || + tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE)); + } } } - - DicomMap::LoadMainDicomTags(tags, size, level); - - for (size_t i = 0; i < size; i++) + { - if (registry_.find(tags[i]) == registry_.end()) + std::set<DicomTag> tags; + DicomMap::GetMainDicomTags(tags, level); + + for (std::set<DicomTag>::const_iterator + tag = tags.begin(); tag != tags.end(); ++tag) { - registry_[tags[i]] = TagInfo(level, DicomTagType_Main); + if (registry_.find(*tag) == registry_.end()) + { + registry_[*tag] = TagInfo(level, DicomTagType_Main); + } } } } @@ -494,7 +508,16 @@ Logging::Flush(); boost::mutex::scoped_lock lock(that->mutex_); - that->db_.FlushToDisk(); + + try + { + that->db_.FlushToDisk(); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot flush the SQLite database to the disk (is your filesystem full?)"; + } + count = 0; }
--- a/OrthancServer/ServerIndex.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerIndex.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -65,14 +65,14 @@ boost::thread flushThread_; boost::thread unstableResourcesMonitorThread_; - std::auto_ptr<Listener> listener_; + std::unique_ptr<Listener> listener_; IDatabaseWrapper& db_; LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload> unstableResources_; uint64_t maximumStorageSize_; unsigned int maximumPatients_; bool overwrite_; - std::auto_ptr<MainDicomTagsRegistry> mainDicomTagsRegistry_; + std::unique_ptr<MainDicomTagsRegistry> mainDicomTagsRegistry_; static void FlushThread(ServerIndex* that, unsigned int threadSleep);
--- a/OrthancServer/ServerIndexChange.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerIndexChange.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/ArchiveJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/ArchiveJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -276,7 +276,7 @@ else if (previous == resources_.end()) { // This is the first time we meet this resource - std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); + std::unique_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); child->Add(index, resource); resources_[id] = child.release(); } @@ -308,7 +308,7 @@ std::list<std::string> children; index.GetChildren(children, it->first); - std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); + std::unique_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); for (std::list<std::string>::const_iterator it2 = children.begin(); it2 != children.end(); ++it2) @@ -695,12 +695,12 @@ class ArchiveJob::ZipWriterIterator : public boost::noncopyable { private: - TemporaryFile& target_; - ServerContext& context_; - ZipCommands commands_; - std::auto_ptr<HierarchicalZipWriter> zip_; - std::auto_ptr<DicomDirWriter> dicomDir_; - bool isMedia_; + TemporaryFile& target_; + ServerContext& context_; + ZipCommands commands_; + std::unique_ptr<HierarchicalZipWriter> zip_; + std::unique_ptr<DicomDirWriter> dicomDir_; + bool isMedia_; public: ZipWriterIterator(TemporaryFile& target, @@ -902,7 +902,7 @@ class DynamicTemporaryFile : public IDynamicObject { private: - std::auto_ptr<TemporaryFile> file_; + std::unique_ptr<TemporaryFile> file_; public: DynamicTemporaryFile(TemporaryFile* f) : file_(f) @@ -935,7 +935,7 @@ } - JobStepResult ArchiveJob::Step() + JobStepResult ArchiveJob::Step(const std::string& jobId) { assert(writer_.get() != NULL);
--- a/OrthancServer/ServerJobs/ArchiveJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/ArchiveJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "../../Core/Compatibility.h" #include "../../Core/JobsEngine/IJob.h" #include "../../Core/TemporaryFile.h" @@ -55,7 +56,7 @@ class ZipWriterIterator; boost::shared_ptr<TemporaryFile> synchronousTarget_; - std::auto_ptr<TemporaryFile> asynchronousTarget_; + std::unique_ptr<TemporaryFile> asynchronousTarget_; ServerContext& context_; boost::shared_ptr<ArchiveIndex> archive_; bool isMedia_; @@ -88,29 +89,29 @@ void AddResource(const std::string& publicId); - virtual void Reset(); + virtual void Reset() ORTHANC_OVERRIDE; - virtual void Start(); + virtual void Start() ORTHANC_OVERRIDE; - virtual JobStepResult Step(); + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE; - virtual void Stop(JobStopReason reason) + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE { } - virtual float GetProgress(); + virtual float GetProgress() ORTHANC_OVERRIDE; - virtual void GetJobType(std::string& target); + virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE; - virtual void GetPublicContent(Json::Value& value); + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; - virtual bool Serialize(Json::Value& value) + virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE { return false; // Cannot serialize this kind of job } virtual bool GetOutput(std::string& output, MimeType& mime, - const std::string& key); + const std::string& key) ORTHANC_OVERRIDE; }; }
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,9 +34,11 @@ #include "../PrecompiledHeadersServer.h" #include "DicomModalityStoreJob.h" +#include "../../Core/Compatibility.h" #include "../../Core/Logging.h" #include "../../Core/SerializationToolbox.h" #include "../ServerContext.h" +#include "../StorageCommitmentReports.h" namespace Orthanc @@ -71,14 +73,47 @@ LOG(WARNING) << "An instance was removed after the job was issued: " << instance; return false; } + + std::string sopClassUid, sopInstanceUid; if (HasMoveOriginator()) { - connection_->Store(dicom, moveOriginatorAet_, moveOriginatorId_); + connection_->Store(sopClassUid, sopInstanceUid, dicom, moveOriginatorAet_, moveOriginatorId_); } else { - connection_->Store(dicom); + connection_->Store(sopClassUid, sopInstanceUid, dicom); + } + + if (storageCommitment_) + { + sopClassUids_.push_back(sopClassUid); + sopInstanceUids_.push_back(sopInstanceUid); + + if (sopClassUids_.size() != sopInstanceUids_.size() || + sopClassUids_.size() > GetInstancesCount()) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (sopClassUids_.size() == GetInstancesCount()) + { + const std::string& remoteAet = remote_.GetApplicationEntityTitle(); + + LOG(INFO) << "Sending storage commitment request to modality: " << remoteAet; + + // Create a "pending" storage commitment report BEFORE the + // actual SCU call in order to avoid race conditions + context_.GetStorageCommitmentReports().Store( + transactionUid_, new StorageCommitmentReports::Report(remoteAet)); + + assert(IsStarted()); + OpenConnection(); + + std::vector<std::string> a(sopClassUids_.begin(), sopClassUids_.end()); + std::vector<std::string> b(sopInstanceUids_.begin(), sopInstanceUids_.end()); + connection_->RequestStorageCommitment(transactionUid_, a, b); + } } //boost::this_thread::sleep(boost::posix_time::milliseconds(500)); @@ -96,8 +131,10 @@ DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context) : context_(context), localAet_("ORTHANC"), - moveOriginatorId_(0) // By default, not a C-MOVE + moveOriginatorId_(0), // By default, not a C-MOVE + storageCommitment_(false) // By default, no storage commitment { + ResetStorageCommitment(); } @@ -178,6 +215,38 @@ } + void DicomModalityStoreJob::ResetStorageCommitment() + { + if (storageCommitment_) + { + transactionUid_ = Toolbox::GenerateDicomPrivateUniqueIdentifier(); + sopClassUids_.clear(); + sopInstanceUids_.clear(); + } + } + + + void DicomModalityStoreJob::Reset() + { + SetOfInstancesJob::Reset(); + + /** + * "After the N-EVENT-REPORT has been sent, the Transaction UID is + * no longer active and shall not be reused for other + * transactions." => Need to reset the transaction UID here + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html + **/ + ResetStorageCommitment(); + } + + + void DicomModalityStoreJob::EnableStorageCommitment(bool enabled) + { + storageCommitment_ = enabled; + ResetStorageCommitment(); + } + + void DicomModalityStoreJob::GetPublicContent(Json::Value& value) { SetOfInstancesJob::GetPublicContent(value); @@ -190,6 +259,11 @@ value["MoveOriginatorAET"] = GetMoveOriginatorAet(); value["MoveOriginatorID"] = GetMoveOriginatorId(); } + + if (storageCommitment_) + { + value["StorageCommitmentTransactionUID"] = transactionUid_; + } } @@ -197,6 +271,7 @@ static const char* REMOTE = "Remote"; static const char* MOVE_ORIGINATOR_AET = "MoveOriginatorAet"; static const char* MOVE_ORIGINATOR_ID = "MoveOriginatorId"; + static const char* STORAGE_COMMITMENT = "StorageCommitment"; DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context, @@ -209,6 +284,7 @@ moveOriginatorAet_ = SerializationToolbox::ReadString(serialized, MOVE_ORIGINATOR_AET); moveOriginatorId_ = static_cast<uint16_t> (SerializationToolbox::ReadUnsignedInteger(serialized, MOVE_ORIGINATOR_ID)); + EnableStorageCommitment(SerializationToolbox::ReadBoolean(serialized, STORAGE_COMMITMENT)); } @@ -224,6 +300,7 @@ remote_.Serialize(target[REMOTE], true /* force advanced format */); target[MOVE_ORIGINATOR_AET] = moveOriginatorAet_; target[MOVE_ORIGINATOR_ID] = moveOriginatorId_; + target[STORAGE_COMMITMENT] = storageCommitment_; return true; } }
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "../../Core/Compatibility.h" #include "../../Core/JobsEngine/SetOfInstancesJob.h" #include "../../Core/DicomNetworking/DicomUserConnection.h" @@ -43,19 +44,27 @@ class DicomModalityStoreJob : public SetOfInstancesJob { private: - ServerContext& context_; - std::string localAet_; - RemoteModalityParameters remote_; - std::string moveOriginatorAet_; - uint16_t moveOriginatorId_; - std::auto_ptr<DicomUserConnection> connection_; + ServerContext& context_; + std::string localAet_; + RemoteModalityParameters remote_; + std::string moveOriginatorAet_; + uint16_t moveOriginatorId_; + std::unique_ptr<DicomUserConnection> connection_; + bool storageCommitment_; + + // For storage commitment + std::string transactionUid_; + std::list<std::string> sopInstanceUids_; + std::list<std::string> sopClassUids_; void OpenConnection(); + void ResetStorageCommitment(); + protected: - virtual bool HandleInstance(const std::string& instance); + virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE; - virtual bool HandleTrailingStep(); + virtual bool HandleTrailingStep() ORTHANC_OVERRIDE; public: DicomModalityStoreJob(ServerContext& context); @@ -89,15 +98,19 @@ void SetMoveOriginator(const std::string& aet, int id); - virtual void Stop(JobStopReason reason); + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE; - virtual void GetJobType(std::string& target) + virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE { target = "DicomModalityStore"; } - virtual void GetPublicContent(Json::Value& value); + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; + + virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE; - virtual bool Serialize(Json::Value& target); + virtual void Reset() ORTHANC_OVERRIDE; + + void EnableStorageCommitment(bool enabled); }; }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -46,8 +46,8 @@ class DicomMoveScuJob::Command : public SetOfCommandsJob::ICommand { private: - DicomMoveScuJob& that_; - std::auto_ptr<DicomMap> findAnswer_; + DicomMoveScuJob& that_; + std::unique_ptr<DicomMap> findAnswer_; public: Command(DicomMoveScuJob& that, @@ -57,13 +57,13 @@ { } - virtual bool Execute() + virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE { that_.Retrieve(*findAnswer_); return true; } - virtual void Serialize(Json::Value& target) const + virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE { findAnswer_->Serialize(target); }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "../../Core/Compatibility.h" #include "../../Core/JobsEngine/SetOfCommandsJob.h" #include "../../Core/DicomNetworking/DicomUserConnection.h" @@ -48,12 +49,12 @@ class Command; class Unserializer; - ServerContext& context_; - std::string localAet_; - std::string targetAet_; - RemoteModalityParameters remote_; - std::auto_ptr<DicomUserConnection> connection_; - Json::Value query_; + ServerContext& context_; + std::string localAet_; + std::string targetAet_; + RemoteModalityParameters remote_; + std::unique_ptr<DicomUserConnection> connection_; + Json::Value query_; void Retrieve(const DicomMap& findAnswer);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/IStorageCommitmentFactory.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 <vector> + +namespace Orthanc +{ + class IStorageCommitmentFactory : public boost::noncopyable + { + public: + class ILookupHandler : public boost::noncopyable + { + public: + virtual ~ILookupHandler() + { + } + + virtual StorageCommitmentFailureReason Lookup(const std::string& sopClassUid, + const std::string& sopInstanceUid) = 0; + }; + + virtual ~IStorageCommitmentFactory() + { + } + + virtual ILookupHandler* CreateStorageCommitment(const std::string& jobId, + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) = 0; + }; +}
--- a/OrthancServer/ServerJobs/LuaJobManager.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/LuaJobManager.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/LuaJobManager.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/LuaJobManager.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -69,11 +69,11 @@ class Lock : public boost::noncopyable { private: - LuaJobManager& that_; - boost::mutex::scoped_lock lock_; - JobsEngine& engine_; - std::auto_ptr<SequenceOfOperationsJob::Lock> jobLock_; - bool isNewJob_; + LuaJobManager& that_; + boost::mutex::scoped_lock lock_; + JobsEngine& engine_; + std::unique_ptr<SequenceOfOperationsJob::Lock> jobLock_; + bool isNewJob_; public: Lock(LuaJobManager& that,
--- a/OrthancServer/ServerJobs/MergeStudyJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/MergeStudyJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -85,7 +85,7 @@ * Retrieve the DICOM instance to be modified **/ - std::auto_ptr<ParsedDicomFile> modified; + std::unique_ptr<ParsedDicomFile> modified; try {
--- a/OrthancServer/ServerJobs/MergeStudyJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/MergeStudyJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -94,7 +94,7 @@ LOG(INFO) << "Lua: Modifying instance " << instance.GetId(); - std::auto_ptr<ParsedDicomFile> modified; + std::unique_ptr<ParsedDicomFile> modified; { ServerContext::DicomCacheLocker lock(context_, instance.GetId());
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,8 +33,9 @@ #pragma once +#include "../../../Core/Compatibility.h" +#include "../../../Core/DicomParsing/DicomModification.h" #include "../../../Core/JobsEngine/Operations/IJobOperation.h" -#include "../../../Core/DicomParsing/DicomModification.h" namespace Orthanc { @@ -43,9 +44,9 @@ class ModifyInstanceOperation : public IJobOperation { private: - ServerContext& context_; - RequestOrigin origin_; - std::auto_ptr<DicomModification> modification_; + ServerContext& context_; + RequestOrigin origin_; + std::unique_ptr<DicomModification> modification_; public: ModifyInstanceOperation(ServerContext& context,
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -46,7 +46,7 @@ const JobOperationValue& input, IDicomConnectionManager& connectionManager) { - std::auto_ptr<IDicomConnectionManager::IResource> resource + std::unique_ptr<IDicomConnectionManager::IResource> resource (connectionManager.AcquireConnection(localAet_, modality_)); if (resource.get() == NULL) @@ -70,7 +70,9 @@ { std::string dicom; instance.ReadDicom(dicom); - resource->GetConnection().Store(dicom); + + std::string sopClassUid, sopInstanceUid; // Unused + resource->GetConnection().Store(sopClassUid, sopInstanceUid, dicom); } catch (OrthancException& e) {
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -81,7 +81,7 @@ arguments.reserve(arguments.size() + postArguments_.size() + 1); - std::auto_ptr<TemporaryFile> tmp; + std::unique_ptr<TemporaryFile> tmp; switch (input.GetType()) {
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -49,10 +49,11 @@ #include "DicomModalityStoreJob.h" #include "DicomMoveScuJob.h" +#include "MergeStudyJob.h" #include "OrthancPeerStoreJob.h" #include "ResourceModificationJob.h" -#include "MergeStudyJob.h" #include "SplitStudyJob.h" +#include "StorageCommitmentScpJob.h" namespace Orthanc @@ -64,7 +65,7 @@ #if ORTHANC_ENABLE_PLUGINS == 1 if (context_.HasPlugins()) { - std::auto_ptr<IJob> job(context_.GetPlugins().UnserializeJob(type, source)); + std::unique_ptr<IJob> job(context_.GetPlugins().UnserializeJob(type, source)); if (job.get() != NULL) { return job.release(); @@ -96,6 +97,10 @@ { return new DicomMoveScuJob(context_, source); } + else if (type == "StorageCommitmentScp") + { + return new StorageCommitmentScpJob(context_, source); + } else { return GenericJobUnserializer::UnserializeJob(source);
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/OrthancPeerStoreJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,7 @@ #pragma once +#include "../../Core/Compatibility.h" #include "../../Core/JobsEngine/SetOfInstancesJob.h" #include "../../Core/HttpClient.h" @@ -44,9 +45,9 @@ class OrthancPeerStoreJob : public SetOfInstancesJob { private: - ServerContext& context_; - WebServiceParameters peer_; - std::auto_ptr<HttpClient> client_; + ServerContext& context_; + WebServiceParameters peer_; + std::unique_ptr<HttpClient> client_; protected: virtual bool HandleInstance(const std::string& instance);
--- a/OrthancServer/ServerJobs/ResourceModificationJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -147,8 +147,8 @@ * Retrieve the original instance from the DICOM cache. **/ - std::auto_ptr<DicomInstanceHasher> originalHasher; - std::auto_ptr<ParsedDicomFile> modified; + std::unique_ptr<DicomInstanceHasher> originalHasher; + std::unique_ptr<ParsedDicomFile> modified; try {
--- a/OrthancServer/ServerJobs/ResourceModificationJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -46,11 +46,11 @@ private: class Output; - ServerContext& context_; - std::auto_ptr<DicomModification> modification_; - boost::shared_ptr<Output> output_; - bool isAnonymization_; - DicomInstanceOrigin origin_; + ServerContext& context_; + std::unique_ptr<DicomModification> modification_; + boost::shared_ptr<Output> output_; + bool isAnonymization_; + DicomInstanceOrigin origin_; protected: virtual bool HandleInstance(const std::string& instance);
--- a/OrthancServer/ServerJobs/SplitStudyJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -71,7 +71,7 @@ * Retrieve the DICOM instance to be modified **/ - std::auto_ptr<ParsedDicomFile> modified; + std::unique_ptr<ParsedDicomFile> modified; try {
--- a/OrthancServer/ServerJobs/SplitStudyJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerJobs/SplitStudyJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,454 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "../PrecompiledHeadersServer.h" +#include "StorageCommitmentScpJob.h" + +#include "../../Core/DicomNetworking/DicomUserConnection.h" +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" +#include "../../Core/SerializationToolbox.h" +#include "../OrthancConfiguration.h" +#include "../ServerContext.h" + + +static const char* ANSWER = "Answer"; +static const char* CALLED_AET = "CalledAet"; +static const char* INDEX = "Index"; +static const char* LOOKUP = "Lookup"; +static const char* REMOTE_MODALITY = "RemoteModality"; +static const char* SETUP = "Setup"; +static const char* SOP_CLASS_UIDS = "SopClassUids"; +static const char* SOP_INSTANCE_UIDS = "SopInstanceUids"; +static const char* TRANSACTION_UID = "TransactionUid"; +static const char* TYPE = "Type"; + + + +namespace Orthanc +{ + class StorageCommitmentScpJob::StorageCommitmentCommand : public SetOfCommandsJob::ICommand + { + public: + virtual CommandType GetType() const = 0; + }; + + + class StorageCommitmentScpJob::SetupCommand : public StorageCommitmentCommand + { + private: + StorageCommitmentScpJob& that_; + + public: + SetupCommand(StorageCommitmentScpJob& that) : + that_(that) + { + } + + virtual CommandType GetType() const ORTHANC_OVERRIDE + { + return CommandType_Setup; + } + + virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE + { + that_.Setup(jobId); + return true; + } + + virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE + { + target = Json::objectValue; + target[TYPE] = SETUP; + } + }; + + + class StorageCommitmentScpJob::LookupCommand : public StorageCommitmentCommand + { + private: + StorageCommitmentScpJob& that_; + size_t index_; + bool hasFailureReason_; + StorageCommitmentFailureReason failureReason_; + + public: + LookupCommand(StorageCommitmentScpJob& that, + size_t index) : + that_(that), + index_(index), + hasFailureReason_(false) + { + } + + virtual CommandType GetType() const ORTHANC_OVERRIDE + { + return CommandType_Lookup; + } + + virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE + { + failureReason_ = that_.Lookup(index_); + hasFailureReason_ = true; + return true; + } + + size_t GetIndex() const + { + return index_; + } + + StorageCommitmentFailureReason GetFailureReason() const + { + if (hasFailureReason_) + { + return failureReason_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE + { + target = Json::objectValue; + target[TYPE] = LOOKUP; + target[INDEX] = static_cast<unsigned int>(index_); + } + }; + + + class StorageCommitmentScpJob::AnswerCommand : public StorageCommitmentCommand + { + private: + StorageCommitmentScpJob& that_; + + public: + AnswerCommand(StorageCommitmentScpJob& that) : + that_(that) + { + if (that_.ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + that_.ready_ = true; + } + } + + virtual CommandType GetType() const ORTHANC_OVERRIDE + { + return CommandType_Answer; + } + + virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE + { + that_.Answer(); + return true; + } + + virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE + { + target = Json::objectValue; + target[TYPE] = ANSWER; + } + }; + + + class StorageCommitmentScpJob::Unserializer : public SetOfCommandsJob::ICommandUnserializer + { + private: + StorageCommitmentScpJob& that_; + + public: + Unserializer(StorageCommitmentScpJob& that) : + that_(that) + { + that_.ready_ = false; + } + + virtual ICommand* Unserialize(const Json::Value& source) const + { + const std::string type = SerializationToolbox::ReadString(source, TYPE); + + if (type == SETUP) + { + return new SetupCommand(that_); + } + else if (type == LOOKUP) + { + return new LookupCommand(that_, SerializationToolbox::ReadUnsignedInteger(source, INDEX)); + } + else if (type == ANSWER) + { + return new AnswerCommand(that_); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + }; + + + void StorageCommitmentScpJob::CheckInvariants() + { + const size_t n = GetCommandsCount(); + + if (n <= 1) + { + throw OrthancException(ErrorCode_InternalError); + } + + for (size_t i = 0; i < n; i++) + { + const CommandType type = dynamic_cast<const StorageCommitmentCommand&>(GetCommand(i)).GetType(); + + if ((i == 0 && type != CommandType_Setup) || + (i >= 1 && i < n - 1 && type != CommandType_Lookup) || + (i == n - 1 && type != CommandType_Answer)) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (type == CommandType_Lookup) + { + const LookupCommand& lookup = dynamic_cast<const LookupCommand&>(GetCommand(i)); + if (lookup.GetIndex() != i - 1) + { + throw OrthancException(ErrorCode_InternalError); + } + } + } + } + + + void StorageCommitmentScpJob::Setup(const std::string& jobId) + { + CheckInvariants(); + + const std::string& remoteAet = remoteModality_.GetApplicationEntityTitle(); + lookupHandler_.reset(context_.CreateStorageCommitment(jobId, transactionUid_, sopClassUids_, + sopInstanceUids_, remoteAet, calledAet_)); + } + + + StorageCommitmentFailureReason StorageCommitmentScpJob::Lookup(size_t index) + { +#ifndef NDEBUG + CheckInvariants(); +#endif + + if (index >= sopClassUids_.size()) + { + throw OrthancException(ErrorCode_InternalError); + } + else if (lookupHandler_.get() != NULL) + { + return lookupHandler_->Lookup(sopClassUids_[index], sopInstanceUids_[index]); + } + else + { + // This is the default implementation of Orthanc (if no storage + // commitment plugin is installed) + bool success = false; + StorageCommitmentFailureReason reason = + StorageCommitmentFailureReason_NoSuchObjectInstance /* 0x0112 == 274 */; + + try + { + std::vector<std::string> orthancId; + context_.GetIndex().LookupIdentifierExact(orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUids_[index]); + + if (orthancId.size() == 1) + { + std::string a, b; + + // Make sure that the DICOM file can be re-read by DCMTK + // from the file storage, and that the actual SOP + // class/instance UIDs do match + ServerContext::DicomCacheLocker locker(context_, orthancId[0]); + if (locker.GetDicom().GetTagValue(a, DICOM_TAG_SOP_CLASS_UID) && + locker.GetDicom().GetTagValue(b, DICOM_TAG_SOP_INSTANCE_UID) && + b == sopInstanceUids_[index]) + { + if (a == sopClassUids_[index]) + { + success = true; + reason = StorageCommitmentFailureReason_Success; + } + else + { + // Mismatch in the SOP class UID + reason = StorageCommitmentFailureReason_ClassInstanceConflict /* 0x0119 */; + } + } + } + } + catch (OrthancException&) + { + } + + LOG(INFO) << " Storage commitment SCP job: " << (success ? "Success" : "Failure") + << " while looking for " << sopClassUids_[index] << " / " << sopInstanceUids_[index]; + + return reason; + } + } + + + void StorageCommitmentScpJob::Answer() + { + CheckInvariants(); + LOG(INFO) << " Storage commitment SCP job: Sending answer"; + + std::vector<StorageCommitmentFailureReason> failureReasons; + failureReasons.reserve(sopClassUids_.size()); + + for (size_t i = 1; i < GetCommandsCount() - 1; i++) + { + const LookupCommand& lookup = dynamic_cast<const LookupCommand&>(GetCommand(i)); + failureReasons.push_back(lookup.GetFailureReason()); + } + + if (failureReasons.size() != sopClassUids_.size()) + { + throw OrthancException(ErrorCode_InternalError); + } + + DicomUserConnection scu(calledAet_, remoteModality_); + scu.ReportStorageCommitment(transactionUid_, sopClassUids_, sopInstanceUids_, failureReasons); + } + + + StorageCommitmentScpJob::StorageCommitmentScpJob(ServerContext& context, + const std::string& transactionUid, + const std::string& remoteAet, + const std::string& calledAet) : + context_(context), + ready_(false), + transactionUid_(transactionUid), + calledAet_(calledAet) + { + { + OrthancConfiguration::ReaderLock lock; + if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remoteModality_, remoteAet)) + { + throw OrthancException(ErrorCode_InexistentItem, + "Unknown remote modality for storage commitment SCP: " + remoteAet); + } + } + + AddCommand(new SetupCommand(*this)); + } + + + void StorageCommitmentScpJob::Reserve(size_t size) + { + if (ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + sopClassUids_.reserve(size); + sopInstanceUids_.reserve(size); + } + } + + + void StorageCommitmentScpJob::AddInstance(const std::string& sopClassUid, + const std::string& sopInstanceUid) + { + if (ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + assert(sopClassUids_.size() == sopInstanceUids_.size()); + AddCommand(new LookupCommand(*this, sopClassUids_.size())); + sopClassUids_.push_back(sopClassUid); + sopInstanceUids_.push_back(sopInstanceUid); + } + } + + + void StorageCommitmentScpJob::MarkAsReady() + { + AddCommand(new AnswerCommand(*this)); + } + + + void StorageCommitmentScpJob::GetPublicContent(Json::Value& value) + { + SetOfCommandsJob::GetPublicContent(value); + + value["CalledAet"] = calledAet_; + value["RemoteAet"] = remoteModality_.GetApplicationEntityTitle(); + value["TransactionUid"] = transactionUid_; + } + + + StorageCommitmentScpJob::StorageCommitmentScpJob(ServerContext& context, + const Json::Value& serialized) : + SetOfCommandsJob(new Unserializer(*this), serialized), + context_(context) + { + transactionUid_ = SerializationToolbox::ReadString(serialized, TRANSACTION_UID); + remoteModality_ = RemoteModalityParameters(serialized[REMOTE_MODALITY]); + calledAet_ = SerializationToolbox::ReadString(serialized, CALLED_AET); + SerializationToolbox::ReadArrayOfStrings(sopClassUids_, serialized, SOP_CLASS_UIDS); + SerializationToolbox::ReadArrayOfStrings(sopInstanceUids_, serialized, SOP_INSTANCE_UIDS); + } + + + bool StorageCommitmentScpJob::Serialize(Json::Value& target) + { + if (!SetOfCommandsJob::Serialize(target)) + { + return false; + } + else + { + target[TRANSACTION_UID] = transactionUid_; + remoteModality_.Serialize(target[REMOTE_MODALITY], true /* force advanced format */); + target[CALLED_AET] = calledAet_; + SerializationToolbox::WriteArrayOfStrings(target, sopClassUids_, SOP_CLASS_UIDS); + SerializationToolbox::WriteArrayOfStrings(target, sopInstanceUids_, SOP_INSTANCE_UIDS); + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,111 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/Compatibility.h" +#include "../../Core/DicomNetworking/RemoteModalityParameters.h" +#include "../../Core/JobsEngine/SetOfCommandsJob.h" +#include "IStorageCommitmentFactory.h" + +#include <memory> +#include <vector> + +namespace Orthanc +{ + class ServerContext; + + class StorageCommitmentScpJob : public SetOfCommandsJob + { + private: + enum CommandType + { + CommandType_Setup, + CommandType_Lookup, + CommandType_Answer + }; + + class StorageCommitmentCommand; + class SetupCommand; + class LookupCommand; + class AnswerCommand; + class Unserializer; + + ServerContext& context_; + bool ready_; + std::string transactionUid_; + RemoteModalityParameters remoteModality_; + std::string calledAet_; + std::vector<std::string> sopClassUids_; + std::vector<std::string> sopInstanceUids_; + + std::unique_ptr<IStorageCommitmentFactory::ILookupHandler> lookupHandler_; + + void CheckInvariants(); + + void Setup(const std::string& jobId); + + StorageCommitmentFailureReason Lookup(size_t index); + + void Answer(); + + public: + StorageCommitmentScpJob(ServerContext& context, + const std::string& transactionUid, + const std::string& remoteAet, + const std::string& calledAet); + + StorageCommitmentScpJob(ServerContext& context, + const Json::Value& serialized); + + void Reserve(size_t size); + + void AddInstance(const std::string& sopClassUid, + const std::string& sopInstanceUid); + + void MarkAsReady(); + + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE + { + } + + virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE + { + target = "StorageCommitmentScp"; + } + + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; + + virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE; + }; +}
--- a/OrthancServer/ServerToolbox.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerToolbox.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerToolbox.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/ServerToolbox.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/SliceOrdering.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/SliceOrdering.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/SliceOrdering.h Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/SliceOrdering.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/StorageCommitmentReports.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,272 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 "PrecompiledHeadersServer.h" +#include "StorageCommitmentReports.h" + +#include "../Core/OrthancException.h" + +namespace Orthanc +{ + void StorageCommitmentReports::Report::MarkAsComplete() + { + if (isComplete_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + isComplete_ = true; + } + } + + void StorageCommitmentReports::Report::AddSuccess(const std::string& sopClassUid, + const std::string& sopInstanceUid) + { + if (isComplete_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + Success success; + success.sopClassUid_ = sopClassUid; + success.sopInstanceUid_ = sopInstanceUid; + success_.push_back(success); + } + } + + void StorageCommitmentReports::Report::AddFailure(const std::string& sopClassUid, + const std::string& sopInstanceUid, + StorageCommitmentFailureReason reason) + { + if (isComplete_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + Failure failure; + failure.sopClassUid_ = sopClassUid; + failure.sopInstanceUid_ = sopInstanceUid; + failure.reason_ = reason; + failures_.push_back(failure); + } + } + + + StorageCommitmentReports::Report::Status StorageCommitmentReports::Report::GetStatus() const + { + if (!isComplete_) + { + return Status_Pending; + } + else if (failures_.empty()) + { + return Status_Success; + } + else + { + return Status_Failure; + } + } + + + void StorageCommitmentReports::Report::Format(Json::Value& json) const + { + static const char* const FIELD_STATUS = "Status"; + static const char* const FIELD_SOP_CLASS_UID = "SOPClassUID"; + static const char* const FIELD_SOP_INSTANCE_UID = "SOPInstanceUID"; + static const char* const FIELD_FAILURE_REASON = "FailureReason"; + static const char* const FIELD_DESCRIPTION = "Description"; + static const char* const FIELD_REMOTE_AET = "RemoteAET"; + static const char* const FIELD_SUCCESS = "Success"; + static const char* const FIELD_FAILURES = "Failures"; + + + json = Json::objectValue; + json[FIELD_REMOTE_AET] = remoteAet_; + + bool pending; + + switch (GetStatus()) + { + case Status_Pending: + json[FIELD_STATUS] = "Pending"; + pending = true; + break; + + case Status_Success: + json[FIELD_STATUS] = "Success"; + pending = false; + break; + + case Status_Failure: + json[FIELD_STATUS] = "Failure"; + pending = false; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (!pending) + { + { + Json::Value success = Json::arrayValue; + for (std::list<Success>::const_iterator + it = success_.begin(); it != success_.end(); ++it) + { + Json::Value item = Json::objectValue; + item[FIELD_SOP_CLASS_UID] = it->sopClassUid_; + item[FIELD_SOP_INSTANCE_UID] = it->sopInstanceUid_; + success.append(item); + } + + json[FIELD_SUCCESS] = success; + } + + { + Json::Value failures = Json::arrayValue; + for (std::list<Failure>::const_iterator + it = failures_.begin(); it != failures_.end(); ++it) + { + Json::Value item = Json::objectValue; + item[FIELD_SOP_CLASS_UID] = it->sopClassUid_; + item[FIELD_SOP_INSTANCE_UID] = it->sopInstanceUid_; + item[FIELD_FAILURE_REASON] = it->reason_; + item[FIELD_DESCRIPTION] = EnumerationToString(it->reason_); + failures.append(item); + } + + json[FIELD_FAILURES] = failures; + } + } + } + + + void StorageCommitmentReports::Report::GetSuccessSopInstanceUids( + std::vector<std::string>& target) const + { + target.clear(); + target.reserve(success_.size()); + + for (std::list<Success>::const_iterator + it = success_.begin(); it != success_.end(); ++it) + { + target.push_back(it->sopInstanceUid_); + } + } + + + StorageCommitmentReports::~StorageCommitmentReports() + { + while (!content_.IsEmpty()) + { + Report* report = NULL; + content_.RemoveOldest(report); + + assert(report != NULL); + delete report; + } + } + + + void StorageCommitmentReports::Store(const std::string& transactionUid, + Report* report) + { + std::unique_ptr<Report> protection(report); + + boost::mutex::scoped_lock lock(mutex_); + + { + Report* previous = NULL; + if (content_.Contains(transactionUid, previous)) + { + assert(previous != NULL); + delete previous; + + content_.Invalidate(transactionUid); + } + } + + assert(maxSize_ == 0 || + content_.GetSize() <= maxSize_); + + if (maxSize_ != 0 && + content_.GetSize() == maxSize_) + { + assert(!content_.IsEmpty()); + + Report* oldest = NULL; + content_.RemoveOldest(oldest); + + assert(oldest != NULL); + delete oldest; + } + + assert(maxSize_ == 0 || + content_.GetSize() < maxSize_); + + content_.Add(transactionUid, protection.release()); + } + + + StorageCommitmentReports::Accessor::Accessor(StorageCommitmentReports& that, + const std::string& transactionUid) : + lock_(that.mutex_), + transactionUid_(transactionUid) + { + if (that.content_.Contains(transactionUid, report_)) + { + that.content_.MakeMostRecent(transactionUid); + } + else + { + report_ = NULL; + } + } + + const StorageCommitmentReports::Report& + StorageCommitmentReports::Accessor::GetReport() const + { + if (report_ == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *report_; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/StorageCommitmentReports.h Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,147 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/Cache/LeastRecentlyUsedIndex.h" + +namespace Orthanc +{ + class StorageCommitmentReports + { + public: + class Report : public boost::noncopyable + { + public: + enum Status + { + Status_Success, + Status_Failure, + Status_Pending + }; + + private: + struct Success + { + std::string sopClassUid_; + std::string sopInstanceUid_; + }; + + struct Failure + { + std::string sopClassUid_; + std::string sopInstanceUid_; + StorageCommitmentFailureReason reason_; + }; + + bool isComplete_; + std::list<Success> success_; + std::list<Failure> failures_; + std::string remoteAet_; + + public: + Report(const std::string& remoteAet) : + isComplete_(false), + remoteAet_(remoteAet) + { + } + + const std::string& GetRemoteAet() const + { + return remoteAet_; + } + + void MarkAsComplete(); + + void AddSuccess(const std::string& sopClassUid, + const std::string& sopInstanceUid); + + void AddFailure(const std::string& sopClassUid, + const std::string& sopInstanceUid, + StorageCommitmentFailureReason reason); + + Status GetStatus() const; + + void Format(Json::Value& json) const; + + void GetSuccessSopInstanceUids(std::vector<std::string>& target) const; + }; + + private: + typedef LeastRecentlyUsedIndex<std::string, Report*> Content; + + boost::mutex mutex_; + Content content_; + size_t maxSize_; + + public: + StorageCommitmentReports(size_t maxSize) : + maxSize_(maxSize) + { + } + + ~StorageCommitmentReports(); + + size_t GetMaxSize() const + { + return maxSize_; + } + + void Store(const std::string& transactionUid, + Report* report); // Takes ownership + + class Accessor : public boost::noncopyable + { + private: + boost::mutex::scoped_lock lock_; + std::string transactionUid_; + Report *report_; + + public: + Accessor(StorageCommitmentReports& that, + const std::string& transactionUid); + + const std::string& GetTransactionUid() const + { + return transactionUid_; + } + + bool IsValid() const + { + return report_ != NULL; + } + + const Report& GetReport() const; + }; + }; +}
--- a/OrthancServer/main.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/OrthancServer/main.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,6 +36,7 @@ #include <boost/algorithm/string/predicate.hpp> +#include "../Core/Compatibility.h" #include "../Core/DicomFormat/DicomArray.h" #include "../Core/DicomNetworking/DicomServer.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" @@ -50,7 +51,9 @@ #include "OrthancInitialization.h" #include "OrthancMoveRequestHandler.h" #include "ServerContext.h" +#include "ServerJobs/StorageCommitmentScpJob.h" #include "ServerToolbox.h" +#include "StorageCommitmentReports.h" using namespace Orthanc; @@ -58,11 +61,11 @@ class OrthancStoreRequestHandler : public IStoreRequestHandler { private: - ServerContext& server_; + ServerContext& context_; public: OrthancStoreRequestHandler(ServerContext& context) : - server_(context) + context_(context) { } @@ -84,8 +87,82 @@ toStore.SetJson(dicomJson); std::string id; - server_.Store(id, toStore); + context_.Store(id, toStore); + } + } +}; + + + +class OrthancStorageCommitmentRequestHandler : public IStorageCommitmentRequestHandler +{ +private: + ServerContext& context_; + +public: + OrthancStorageCommitmentRequestHandler(ServerContext& context) : + context_(context) + { + } + + virtual void HandleRequest(const std::string& transactionUid, + const std::vector<std::string>& referencedSopClassUids, + const std::vector<std::string>& referencedSopInstanceUids, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + if (referencedSopClassUids.size() != referencedSopInstanceUids.size()) + { + throw OrthancException(ErrorCode_InternalError); + } + + std::unique_ptr<StorageCommitmentScpJob> job( + new StorageCommitmentScpJob(context_, transactionUid, remoteAet, calledAet)); + + for (size_t i = 0; i < referencedSopClassUids.size(); i++) + { + job->AddInstance(referencedSopClassUids[i], referencedSopInstanceUids[i]); } + + job->MarkAsReady(); + + context_.GetJobsEngine().GetRegistry().Submit(job.release(), 0 /* default priority */); + } + + virtual void HandleReport(const std::string& transactionUid, + const std::vector<std::string>& successSopClassUids, + const std::vector<std::string>& successSopInstanceUids, + const std::vector<std::string>& failedSopClassUids, + const std::vector<std::string>& failedSopInstanceUids, + const std::vector<StorageCommitmentFailureReason>& failureReasons, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + if (successSopClassUids.size() != successSopInstanceUids.size() || + failedSopClassUids.size() != failedSopInstanceUids.size() || + failedSopClassUids.size() != failureReasons.size()) + { + throw OrthancException(ErrorCode_InternalError); + } + + std::unique_ptr<StorageCommitmentReports::Report> report( + new StorageCommitmentReports::Report(remoteAet)); + + for (size_t i = 0; i < successSopClassUids.size(); i++) + { + report->AddSuccess(successSopClassUids[i], successSopInstanceUids[i]); + } + + for (size_t i = 0; i < failedSopClassUids.size(); i++) + { + report->AddFailure(failedSopClassUids[i], failedSopInstanceUids[i], failureReasons[i]); + } + + report->MarkAsComplete(); + + context_.GetStorageCommitmentReports().Store(transactionUid, report.release()); } }; @@ -113,7 +190,8 @@ class MyDicomServerFactory : public IStoreRequestHandlerFactory, public IFindRequestHandlerFactory, - public IMoveRequestHandlerFactory + public IMoveRequestHandlerFactory, + public IStorageCommitmentRequestHandlerFactory { private: ServerContext& context_; @@ -130,7 +208,7 @@ virtual IFindRequestHandler* ConstructFindRequestHandler() { - std::auto_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_)); + std::unique_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_)); { OrthancConfiguration::ReaderLock lock; @@ -166,6 +244,11 @@ return new OrthancMoveRequestHandler(context_); } + virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler() + { + return new OrthancStorageCommitmentRequestHandler(context_); + } + void Done() { } @@ -276,6 +359,10 @@ configuration = "Mpeg2TransferSyntaxAccepted"; break; + case TransferSyntax_Mpeg4: + configuration = "Mpeg4TransferSyntaxAccepted"; + break; + case TransferSyntax_Rle: configuration = "RleTransferSyntaxAccepted"; break; @@ -543,7 +630,7 @@ std::cout << path << " " << ORTHANC_VERSION << std::endl << "Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl - << "Copyright (C) 2017-2019 Osimis S.A. (Belgium)" << std::endl + << "Copyright (C) 2017-2020 Osimis S.A. (Belgium)" << std::endl << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>, with OpenSSL exception." << std::endl << "This is free software: you are free to change and redistribute it." << std::endl << "There is NO WARRANTY, to the extent permitted by law." << std::endl @@ -672,6 +759,7 @@ PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series"); PrintErrorCode(ErrorCode_NoWorklistHandler, "No request handler factory for DICOM C-Find Modality SCP"); PrintErrorCode(ErrorCode_AlreadyExistingTag, "Cannot override the value of a tag that already exists"); + PrintErrorCode(ErrorCode_NoStorageCommitmentHandler, "No request handler factory for DICOM N-ACTION SCP (storage commitment)"); PrintErrorCode(ErrorCode_UnsupportedMediaType, "Unsupported media type"); } @@ -966,6 +1054,7 @@ dicomServer.SetStoreRequestHandlerFactory(serverFactory); dicomServer.SetMoveRequestHandlerFactory(serverFactory); dicomServer.SetFindRequestHandlerFactory(serverFactory); + dicomServer.SetStorageCommitmentRequestHandlerFactory(serverFactory); { OrthancConfiguration::ReaderLock lock; @@ -1290,8 +1379,8 @@ bool upgradeDatabase, bool loadJobsFromDatabase) { - std::auto_ptr<IDatabaseWrapper> databasePtr; - std::auto_ptr<IStorageArea> storage; + std::unique_ptr<IDatabaseWrapper> databasePtr; + std::unique_ptr<IStorageArea> storage; #if ORTHANC_ENABLE_PLUGINS == 1 OrthancPlugins plugins;
--- a/Plugins/Engine/IPluginServiceProvider.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/IPluginServiceProvider.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/OrthancPluginDatabase.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/OrthancPluginDatabase.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/OrthancPluginDatabase.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/OrthancPluginDatabase.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/OrthancPlugins.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -367,7 +367,7 @@ public: DicomWebBinaryFormatter(const _OrthancPluginEncodeDicomWeb& parameters) : - callback_(parameters.callback) + callback_(parameters.callback) { } @@ -435,7 +435,7 @@ }; HttpOutput& output_; - std::auto_ptr<std::string> errorDetails_; + std::unique_ptr<std::string> errorDetails_; bool logDetails_; MultipartState multipartState_; std::string multipartSubType_; @@ -665,8 +665,8 @@ public: ChunkedRestCallback(_OrthancPluginChunkedRestCallback parameters) : - parameters_(parameters), - regex_(parameters.pathRegularExpression) + parameters_(parameters), + regex_(parameters.pathRegularExpression) { } @@ -682,6 +682,110 @@ }; + + class StorageCommitmentScp : public IStorageCommitmentFactory + { + private: + class Handler : public IStorageCommitmentFactory::ILookupHandler + { + private: + _OrthancPluginRegisterStorageCommitmentScpCallback parameters_; + void* handler_; + + public: + Handler(_OrthancPluginRegisterStorageCommitmentScpCallback parameters, + void* handler) : + parameters_(parameters), + handler_(handler) + { + if (handler == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + virtual ~Handler() + { + assert(handler_ != NULL); + parameters_.destructor(handler_); + handler_ = NULL; + } + + virtual StorageCommitmentFailureReason Lookup(const std::string& sopClassUid, + const std::string& sopInstanceUid) + { + assert(handler_ != NULL); + OrthancPluginStorageCommitmentFailureReason reason = + OrthancPluginStorageCommitmentFailureReason_Success; + OrthancPluginErrorCode error = parameters_.lookup( + &reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str()); + if (error == OrthancPluginErrorCode_Success) + { + return Plugins::Convert(reason); + } + else + { + throw OrthancException(static_cast<ErrorCode>(error)); + } + } + }; + + _OrthancPluginRegisterStorageCommitmentScpCallback parameters_; + + public: + StorageCommitmentScp(_OrthancPluginRegisterStorageCommitmentScpCallback parameters) : + parameters_(parameters) + { + } + + virtual ILookupHandler* CreateStorageCommitment( + const std::string& jobId, + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE + { + const size_t n = sopClassUids.size(); + + if (sopInstanceUids.size() != n) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::vector<const char*> a, b; + a.resize(n); + b.resize(n); + + for (size_t i = 0; i < n; i++) + { + a[i] = sopClassUids[i].c_str(); + b[i] = sopInstanceUids[i].c_str(); + } + + void* handler = NULL; + OrthancPluginErrorCode error = parameters_.factory( + &handler, jobId.c_str(), transactionUid.c_str(), + a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n), + remoteAet.c_str(), calledAet.c_str()); + + if (error != OrthancPluginErrorCode_Success) + { + throw OrthancException(static_cast<ErrorCode>(error)); + } + else if (handler == NULL) + { + // This plugin won't handle this storage commitment request + return NULL; + } + else + { + return new Handler(parameters_, handler); + } + } + }; + + class ServerContextLock { private: @@ -690,8 +794,8 @@ public: ServerContextLock(PImpl& that) : - lock_(that.contextMutex_), - context_(that.context_) + lock_(that.contextMutex_), + context_(that.context_) { if (context_ == NULL) { @@ -724,6 +828,7 @@ typedef std::list<OrthancPluginDecodeImageCallback> DecodeImageCallbacks; typedef std::list<OrthancPluginJobsUnserializer> JobsUnserializers; typedef std::list<OrthancPluginRefreshMetricsCallback> RefreshMetricsCallbacks; + typedef std::list<StorageCommitmentScp*> StorageCommitmentScpCallbacks; typedef std::map<Property, std::string> Properties; PluginsManager manager_; @@ -740,7 +845,8 @@ IncomingHttpRequestFilters incomingHttpRequestFilters_; IncomingHttpRequestFilters2 incomingHttpRequestFilters2_; RefreshMetricsCallbacks refreshMetricsCallbacks_; - std::auto_ptr<StorageAreaFactory> storageArea_; + StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_; + std::unique_ptr<StorageAreaFactory> storageArea_; boost::recursive_mutex restCallbackMutex_; boost::recursive_mutex storedCallbackMutex_; @@ -750,12 +856,13 @@ boost::mutex decodeImageCallbackMutex_; boost::mutex jobsUnserializersMutex_; boost::mutex refreshMetricsMutex_; + boost::mutex storageCommitmentScpMutex_; boost::recursive_mutex invokeServiceMutex_; Properties properties_; int argc_; char** argv_; - std::auto_ptr<OrthancPluginDatabase> database_; + std::unique_ptr<OrthancPluginDatabase> database_; PluginsErrorDictionary dictionary_; PImpl() : @@ -775,8 +882,8 @@ { private: OrthancPlugins& that_; - std::auto_ptr<HierarchicalMatcher> matcher_; - std::auto_ptr<ParsedDicomFile> filtered_; + std::unique_ptr<HierarchicalMatcher> matcher_; + std::unique_ptr<ParsedDicomFile> filtered_; ParsedDicomFile* currentQuery_; void Reset() @@ -824,7 +931,8 @@ Json::Value target; call.ExecuteToJson(target, true); - filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None)); + filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None, + "" /* no private creator */)); currentQuery_ = filtered_.get(); } } @@ -888,7 +996,7 @@ } ParsedDicomFile f(dicom, size); - std::auto_ptr<ParsedDicomFile> summary(matcher_->Extract(f)); + std::unique_ptr<ParsedDicomFile> summary(matcher_->Extract(f)); reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary); } }; @@ -898,7 +1006,7 @@ { private: OrthancPlugins& that_; - std::auto_ptr<DicomArray> currentQuery_; + std::unique_ptr<DicomArray> currentQuery_; void Reset() { @@ -1260,6 +1368,7 @@ sizeof(int32_t) != sizeof(OrthancPluginConstraintType) || sizeof(int32_t) != sizeof(OrthancPluginMetricsType) || sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) || + sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) || static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) || static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) || static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) || @@ -1303,6 +1412,13 @@ { delete *it; } + + for (PImpl::StorageCommitmentScpCallbacks::iterator + it = pimpl_->storageCommitmentScpCallbacks_.begin(); + it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it) + { + delete *it; + } } @@ -1350,7 +1466,7 @@ public: RestCallbackMatcher(const UriComponents& uri) : - flatUri_(Toolbox::FlattenUri(uri)) + flatUri_(Toolbox::FlattenUri(uri)) { } @@ -1863,6 +1979,18 @@ } + void OrthancPlugins::RegisterStorageCommitmentScpCallback(const void* parameters) + { + const _OrthancPluginRegisterStorageCommitmentScpCallback& p = + *reinterpret_cast<const _OrthancPluginRegisterStorageCommitmentScpCallback*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_); + LOG(INFO) << "Plugin has registered a storage commitment callback"; + + pimpl_->storageCommitmentScpCallbacks_.push_back(new PImpl::StorageCommitmentScp(p)); + } + + void OrthancPlugins::AnswerBuffer(const void* parameters) { const _OrthancPluginAnswerBuffer& p = @@ -2355,7 +2483,7 @@ std::string result; { - std::auto_ptr<DeflateBaseCompressor> compressor; + std::unique_ptr<DeflateBaseCompressor> compressor; switch (p.compression) { @@ -2405,14 +2533,14 @@ } - static OrthancPluginImage* ReturnImage(std::auto_ptr<ImageAccessor>& image) + static OrthancPluginImage* ReturnImage(std::unique_ptr<ImageAccessor>& image) { // Images returned to plugins are assumed to be writeable. If the // input image is read-only, we return a copy so that it can be modified. if (image->IsReadOnly()) { - std::auto_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false)); + std::unique_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false)); ImageProcessing::Copy(*copy, *image); image.reset(NULL); return reinterpret_cast<OrthancPluginImage*>(copy.release()); @@ -2428,7 +2556,7 @@ { const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters); - std::auto_ptr<ImageAccessor> image; + std::unique_ptr<ImageAccessor> image; switch (p.format) { @@ -2812,7 +2940,7 @@ const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast<const _OrthancPluginConvertPixelFormat*>(parameters); const ImageAccessor& source = *reinterpret_cast<const ImageAccessor*>(p.source); - std::auto_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false)); + std::unique_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false)); ImageProcessing::Convert(*target, source); *(p.target) = ReturnImage(target); @@ -2865,7 +2993,7 @@ const _OrthancPluginDicomToJson& p = *reinterpret_cast<const _OrthancPluginDicomToJson*>(parameters); - std::auto_ptr<ParsedDicomFile> dicom; + std::unique_ptr<ParsedDicomFile> dicom; if (service == _OrthancPluginService_DicomBufferToJson) { @@ -2921,8 +3049,18 @@ std::string dicom; { - std::auto_ptr<ParsedDicomFile> file - (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(p.flags))); + // Fix issue 168 (Plugins can't read private tags from the + // configuration file) + // https://bitbucket.org/sjodogne/orthanc/issues/168/ + std::string privateCreator; + { + OrthancConfiguration::ReaderLock lock; + privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator(); + } + + std::unique_ptr<ParsedDicomFile> file + (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(p.flags), + privateCreator)); if (p.pixelData) { @@ -2984,7 +3122,7 @@ const _OrthancPluginCreateImage& p = *reinterpret_cast<const _OrthancPluginCreateImage*>(parameters); - std::auto_ptr<ImageAccessor> result; + std::unique_ptr<ImageAccessor> result; switch (service) { @@ -3095,7 +3233,25 @@ DcmTagKey tag2(tag.GetGroup(), tag.GetElement()); DictionaryReadLocker locker; - const DcmDictEntry* entry = locker->findEntry(tag2, NULL); + const DcmDictEntry* entry = NULL; + + if (tag.IsPrivate()) + { + // Fix issue 168 (Plugins can't read private tags from the + // configuration file) + // https://bitbucket.org/sjodogne/orthanc/issues/168/ + std::string privateCreator; + { + OrthancConfiguration::ReaderLock lock; + privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator(); + } + + entry = locker->findEntry(tag2, privateCreator.c_str()); + } + else + { + entry = locker->findEntry(tag2, NULL); + } if (entry == NULL) { @@ -3882,6 +4038,10 @@ RegisterRefreshMetricsCallback(parameters); return true; + case _OrthancPluginService_RegisterStorageCommitmentScpCallback: + RegisterStorageCommitmentScpCallback(parameters); + return true; + case _OrthancPluginService_RegisterStorageArea: { LOG(INFO) << "Plugin has registered a custom storage area"; @@ -4452,7 +4612,7 @@ }; - bool OrthancPlugins::CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target, + bool OrthancPlugins::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username, @@ -4539,4 +4699,32 @@ } } } + + + IStorageCommitmentFactory::ILookupHandler* OrthancPlugins::CreateStorageCommitment( + const std::string& jobId, + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) + { + boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_); + + for (PImpl::StorageCommitmentScpCallbacks::iterator + it = pimpl_->storageCommitmentScpCallbacks_.begin(); + it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it) + { + assert(*it != NULL); + IStorageCommitmentFactory::ILookupHandler* handler = (*it)->CreateStorageCommitment + (jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet); + + if (handler != NULL) + { + return handler; + } + } + + return NULL; + } }
--- a/Plugins/Engine/OrthancPlugins.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/OrthancPlugins.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -62,6 +62,7 @@ #include "../../Core/JobsEngine/IJob.h" #include "../../OrthancServer/IDicomImageDecoder.h" #include "../../OrthancServer/IServerListener.h" +#include "../../OrthancServer/ServerJobs/IStorageCommitmentFactory.h" #include "OrthancPluginDatabase.h" #include "PluginsManager.h" @@ -80,7 +81,8 @@ public IDicomImageDecoder, public IIncomingHttpRequestFilter, public IFindRequestHandlerFactory, - public IMoveRequestHandlerFactory + public IMoveRequestHandlerFactory, + public IStorageCommitmentFactory { private: class PImpl; @@ -124,6 +126,8 @@ void RegisterRefreshMetricsCallback(const void* parameters); + void RegisterStorageCommitmentScpCallback(const void* parameters); + void AnswerBuffer(const void* parameters); void Redirect(const void* parameters); @@ -235,20 +239,20 @@ const Arguments& headers, const GetArguments& getArguments, const void* bodyData, - size_t bodySize); + size_t bodySize) ORTHANC_OVERRIDE; virtual bool InvokeService(SharedLibrary& plugin, _OrthancPluginService service, - const void* parameters); + const void* parameters) ORTHANC_OVERRIDE; - virtual void SignalChange(const ServerIndexChange& change); - + virtual void SignalChange(const ServerIndexChange& change) ORTHANC_OVERRIDE; + virtual void SignalStoredInstance(const std::string& instanceId, DicomInstanceToStore& instance, - const Json::Value& simplifiedTags); + const Json::Value& simplifiedTags) ORTHANC_OVERRIDE; virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, - const Json::Value& simplified) + const Json::Value& simplified) ORTHANC_OVERRIDE { return true; // TODO Enable filtering of instances from plugins } @@ -298,7 +302,7 @@ bool HasWorklistHandler(); - virtual IWorklistRequestHandler* ConstructWorklistRequestHandler(); + virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() ORTHANC_OVERRIDE; bool HasCustomImageDecoder(); @@ -311,22 +315,22 @@ virtual ImageAccessor* Decode(const void* dicom, size_t size, - unsigned int frame); + unsigned int frame) ORTHANC_OVERRIDE; virtual bool IsAllowed(HttpMethod method, const char* uri, const char* ip, const char* username, const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::GetArguments& getArguments); + const IHttpHandler::GetArguments& getArguments) ORTHANC_OVERRIDE; bool HasFindHandler(); - virtual IFindRequestHandler* ConstructFindRequestHandler(); + virtual IFindRequestHandler* ConstructFindRequestHandler() ORTHANC_OVERRIDE; bool HasMoveHandler(); - virtual IMoveRequestHandler* ConstructMoveRequestHandler(); + virtual IMoveRequestHandler* ConstructMoveRequestHandler() ORTHANC_OVERRIDE; IJob* UnserializeJob(const std::string& type, const Json::Value& value); @@ -334,13 +338,22 @@ void RefreshMetrics(); // New in Orthanc 1.5.7 - virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target, + virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, RequestOrigin origin, const char* remoteIp, const char* username, HttpMethod method, const UriComponents& uri, - const Arguments& headers); + const Arguments& headers) ORTHANC_OVERRIDE; + + // New in Orthanc 1.6.0 + IStorageCommitmentFactory::ILookupHandler* CreateStorageCommitment( + const std::string& jobId, + const std::string& transactionUid, + const std::vector<std::string>& sopClassUids, + const std::vector<std::string>& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE; }; }
--- a/Plugins/Engine/PluginsEnumerations.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsEnumerations.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -549,5 +549,36 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason) + { + switch (reason) + { + case OrthancPluginStorageCommitmentFailureReason_Success: + return StorageCommitmentFailureReason_Success; + + case OrthancPluginStorageCommitmentFailureReason_ProcessingFailure: + return StorageCommitmentFailureReason_ProcessingFailure; + + case OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance: + return StorageCommitmentFailureReason_NoSuchObjectInstance; + + case OrthancPluginStorageCommitmentFailureReason_ResourceLimitation: + return StorageCommitmentFailureReason_ResourceLimitation; + + case OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported: + return StorageCommitmentFailureReason_ReferencedSOPClassNotSupported; + + case OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict: + return StorageCommitmentFailureReason_ClassInstanceConflict; + + case OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID: + return StorageCommitmentFailureReason_DuplicateTransactionUID; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } } }
--- a/Plugins/Engine/PluginsEnumerations.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsEnumerations.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -79,6 +79,8 @@ OrthancPluginJobStepStatus Convert(JobStepCode step); JobStepCode Convert(OrthancPluginJobStepStatus step); + + StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason); } }
--- a/Plugins/Engine/PluginsErrorDictionary.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsErrorDictionary.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -69,7 +69,7 @@ uint16_t httpStatus, const char* message) { - std::auto_ptr<Error> error(new Error); + std::unique_ptr<Error> error(new Error); error->pluginName_ = PluginsManager::GetPluginName(library); error->pluginCode_ = pluginCode;
--- a/Plugins/Engine/PluginsErrorDictionary.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsErrorDictionary.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/PluginsJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -78,7 +78,7 @@ parameters_.finalize(parameters_.job); } - JobStepResult PluginsJob::Step() + JobStepResult PluginsJob::Step(const std::string& jobId) { OrthancPluginJobStepStatus status = parameters_.step(parameters_.job);
--- a/Plugins/Engine/PluginsJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -51,30 +51,30 @@ virtual ~PluginsJob(); - virtual void Start() + virtual void Start() ORTHANC_OVERRIDE { } - virtual JobStepResult Step(); + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE; - virtual void Reset(); + virtual void Reset() ORTHANC_OVERRIDE; - virtual void Stop(JobStopReason reason); + virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE; - virtual float GetProgress(); + virtual float GetProgress() ORTHANC_OVERRIDE; - virtual void GetJobType(std::string& target) + virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE { target = type_; } - virtual void GetPublicContent(Json::Value& value); + virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; - virtual bool Serialize(Json::Value& value); + virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE; virtual bool GetOutput(std::string& output, MimeType& mime, - const std::string& key) + const std::string& key) ORTHANC_OVERRIDE { // TODO return false;
--- a/Plugins/Engine/PluginsManager.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsManager.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -249,7 +249,7 @@ return; } - std::auto_ptr<Plugin> plugin(new Plugin(*this, path)); + std::unique_ptr<Plugin> plugin(new Plugin(*this, path)); if (!IsOrthancPlugin(plugin->GetSharedLibrary())) {
--- a/Plugins/Engine/PluginsManager.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Engine/PluginsManager.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Thu Mar 19 11:48:30 2020 +0100 @@ -6,7 +6,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Include/orthanc/OrthancCPlugin.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Thu Mar 19 11:48:30 2020 +0100 @@ -26,6 +26,7 @@ * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). + * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). * -# <tt>void OrthancPluginFinalize()</tt>: * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -58,7 +59,7 @@ * @brief Functions to register and manage callbacks by the plugins. * * @defgroup DicomCallbacks DicomCallbacks - * @brief Functions to register and manage DICOM callbacks (worklists, C-Find, C-MOVE). + * @brief Functions to register and manage DICOM callbacks (worklists, C-FIND, C-MOVE, storage commitment). * * @defgroup Orthanc Orthanc * @brief Functions to access the content of the Orthanc server. @@ -77,7 +78,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -122,16 +123,16 @@ #endif #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 -#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 5 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 7 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 6 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) -#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ - (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ - (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ - (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ - (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ +#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) #endif @@ -301,6 +302,7 @@ OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, OrthancPluginErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */, + OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043 /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */, OrthancPluginErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */, _OrthancPluginErrorCode_INTERNAL = 0x7fffffff @@ -450,6 +452,7 @@ _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010, _OrthancPluginService_RegisterRefreshMetricsCallback = 1011, _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -909,14 +912,14 @@ **/ typedef enum { - OrthancPluginMetricsType_Default, /*!< Default metrics */ + OrthancPluginMetricsType_Default = 0, /*!< Default metrics */ /** * This metrics represents a time duration. Orthanc will keep the * maximum value of the metrics over a sliding window of ten * seconds, which is useful if the metrics is sampled frequently. **/ - OrthancPluginMetricsType_Timer + OrthancPluginMetricsType_Timer = 1 } OrthancPluginMetricsType; @@ -926,11 +929,47 @@ **/ typedef enum { - OrthancPluginDicomWebBinaryMode_Ignore, /*!< Don't include binary tags */ - OrthancPluginDicomWebBinaryMode_InlineBinary, /*!< Inline encoding using Base64 */ - OrthancPluginDicomWebBinaryMode_BulkDataUri /*!< Use a bulk data URI field */ + OrthancPluginDicomWebBinaryMode_Ignore = 0, /*!< Don't include binary tags */ + OrthancPluginDicomWebBinaryMode_InlineBinary = 1, /*!< Inline encoding using Base64 */ + OrthancPluginDicomWebBinaryMode_BulkDataUri = 2 /*!< Use a bulk data URI field */ } OrthancPluginDicomWebBinaryMode; + + /** + * The available values for the Failure Reason (0008,1197) during + * storage commitment. + * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + **/ + typedef enum + { + OrthancPluginStorageCommitmentFailureReason_Success = 0, + /*!< Success: The DICOM instance is properly stored in the SCP */ + + OrthancPluginStorageCommitmentFailureReason_ProcessingFailure = 1, + /*!< 0110H: A general failure in processing the operation was encountered */ + + OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance = 2, + /*!< 0112H: One or more of the elements in the Referenced SOP + Instance Sequence was not available */ + + OrthancPluginStorageCommitmentFailureReason_ResourceLimitation = 3, + /*!< 0213H: The SCP does not currently have enough resources to + store the requested SOP Instance(s) */ + + OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 4, + /*!< 0122H: Storage Commitment has been requested for a SOP + Instance with a SOP Class that is not supported by the SCP */ + + OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict = 5, + /*!< 0119H: The SOP Class of an element in the Referenced SOP + Instance Sequence did not correspond to the SOP class registered + for this SOP Instance at the SCP */ + + OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID = 6 + /*!< 0131H: The Transaction UID of the Storage Commitment Request + is already in use */ + } OrthancPluginStorageCommitmentFailureReason; + /** @@ -1148,6 +1187,12 @@ * @param type The content type corresponding to this file. * @return 0 if success, other value if error. * @ingroup Callbacks + * + * @warning The "content" buffer *must* have been allocated using + * the "malloc()" function of your C standard library (i.e. nor + * "new[]", neither a pointer to a buffer). The "free()" function of + * your C standard library will automatically be invoked on the + * "content" pointer. **/ typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( void** content, @@ -1539,7 +1584,7 @@ * "levelTagElement", and "levelIndex" arrays. * @param levelTagGroup The group of the parent DICOM tags in the hierarchy. * @param levelTagElement The element of the parent DICOM tags in the hierarchy. - * @param levelIndex The index of the node in the parent sequences of the hiearchy. + * @param levelIndex The index of the node in the parent sequences of the hierarchy. * @param tagGroup The group of the DICOM tag of interest. * @param tagElement The element of the DICOM tag of interest. * @param vr The value representation of the binary DICOM node. @@ -1652,7 +1697,8 @@ sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) || sizeof(int32_t) != sizeof(OrthancPluginConstraintType) || sizeof(int32_t) != sizeof(OrthancPluginMetricsType) || - sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode)) + sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) || + sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason)) { /* Mismatch in the size of the enumerations */ return 0; @@ -2880,7 +2926,10 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param instance The instance of interest. * @param metadata The metadata of interest. - * @return The metadata value if success, NULL if error. + * @return The metadata value if success, NULL if error. Please note that the + * returned string belongs to the instance object and must NOT be + * deallocated. Please make a copy of the string if you wish to access + * it later. * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( @@ -7250,6 +7299,117 @@ } + + /** + * @brief Callback executed by the storage commitment SCP. + * + * Signature of a factory function that creates an object to handle + * one incoming storage commitment request. + * + * @remark The factory receives the list of the SOP class/instance + * UIDs of interest to the remote storage commitment SCU. This gives + * the factory the possibility to start some prefetch process + * upfront in the background, before the handler object is actually + * queried about the status of these DICOM instances. + * + * @param handler Output variable where the factory puts the handler object it created. + * @param jobId ID of the Orthanc job that is responsible for handling + * the storage commitment request. This job will successively look for the + * status of all the individual queried DICOM instances. + * @param transactionUid UID of the storage commitment transaction + * provided by the storage commitment SCU. It contains the value of the + * (0008,1195) DICOM tag. + * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU. + * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU. + * @param countInstances Number of DICOM instances that are queried. This is the size + * of the `sopClassUids` and `sopInstanceUids` arrays. + * @param remoteAet The AET of the storage commitment SCU. + * @param calledAet The AET used by the SCU to contact the storage commitment SCP (i.e. Orthanc). + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory) ( + void** handler /* out */, + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstanceUids, + uint32_t countInstances, + const char* remoteAet, + const char* calledAet); + + + /** + * @brief Callback to free one storage commitment SCP handler. + * + * Signature of a callback function that releases the resources + * allocated by the factory of the storage commitment SCP. The + * handler is the return value of a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. + * + * @param handler The handler object to be destructed. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginStorageCommitmentDestructor) (void* handler); + + + /** + * @brief Callback to get the status of one DICOM instance in the + * storage commitment SCP. + * + * Signature of a callback function that is successively invoked for + * each DICOM instance that is queried by the remote storage + * commitment SCU. The function must be tought of as a method of + * the handler object that was created by a previous call to the + * OrthancPluginStorageCommitmentFactory() callback. After each call + * to this method, the progress of the associated Orthanc job is + * updated. + * + * @param target Output variable where to put the status for the queried instance. + * @param handler The handler object associated with this storage commitment request. + * @param sopClassUid The SOP class UID (0008,0016) of interest. + * @param sopInstanceUid The SOP instance UID (0008,0018) of interest. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentLookup) ( + OrthancPluginStorageCommitmentFailureReason* target, + void* handler, + const char* sopClassUid, + const char* sopInstanceUid); + + + typedef struct + { + OrthancPluginStorageCommitmentFactory factory; + OrthancPluginStorageCommitmentDestructor destructor; + OrthancPluginStorageCommitmentLookup lookup; + } _OrthancPluginRegisterStorageCommitmentScpCallback; + + /** + * @brief Register a callback to handle incoming requests to the storage commitment SCP. + * + * This function registers a callback to handle storage commitment SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param factory Factory function that creates the handler object + * for incoming storage commitment requests. + * @param destructor Destructor function to destroy the handler object. + * @param lookup Callback method to get the status of one DICOM instance. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageCommitmentScpCallback( + OrthancPluginContext* context, + OrthancPluginStorageCommitmentFactory factory, + OrthancPluginStorageCommitmentDestructor destructor, + OrthancPluginStorageCommitmentLookup lookup) + { + _OrthancPluginRegisterStorageCommitmentScpCallback params; + params.factory = factory; + params.destructor = destructor; + params.lookup = lookup; + context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, ¶ms); + } #ifdef __cplusplus }
--- a/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Basic/Plugin.c Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Basic/Plugin.c Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -47,7 +47,7 @@ return OrthancPluginErrorCode_ParameterOutOfRange; } - sprintf(buffer, "Callback on URL [%s] with body [%s]\n", url, request->body); + sprintf(buffer, "Callback on URL [%s] with body [%s]\n", url, (const char*) request->body); OrthancPluginLogWarning(context, buffer); OrthancPluginSetCookie(context, output, "hello", "world");
--- a/Plugins/Samples/Common/DicomDatasetReader.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/DicomDatasetReader.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/DicomDatasetReader.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/DicomDatasetReader.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/DicomPath.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/DicomPath.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,6 +35,8 @@ #include "OrthancPluginException.h" +#include <boost/lexical_cast.hpp> + namespace OrthancPlugins { const DicomPath::Prefix& DicomPath::GetPrefixItem(size_t depth) const @@ -97,4 +99,18 @@ AddToPrefix(sequence2, index2); AddToPrefix(sequence3, index3); } + + + std::string DicomPath::Format() const + { + std::string s; + + for (size_t i = 0; i < GetPrefixLength(); i++) + { + s += (GetPrefixTag(i).FormatHexadecimal() + " / " + + boost::lexical_cast<std::string>(i) + " / "); + } + + return s + GetFinalTag().FormatHexadecimal(); + } }
--- a/Plugins/Samples/Common/DicomPath.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/DicomPath.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -112,5 +112,7 @@ { finalTag_ = tag; } + + std::string Format() const; }; }
--- a/Plugins/Samples/Common/DicomTag.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/DicomTag.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -108,4 +108,12 @@ ORTHANC_PLUGINS_THROW_EXCEPTION(NotImplemented); } } + + + std::string DicomTag::FormatHexadecimal() const + { + char buf[16]; + sprintf(buf, "(%04x,%04x)", group_, element_); + return buf; + } }
--- a/Plugins/Samples/Common/DicomTag.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/DicomTag.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #pragma once #include <stdint.h> +#include <string> namespace OrthancPlugins { @@ -74,6 +75,8 @@ { return !(*this == other); } + + std::string FormatHexadecimal() const; };
--- a/Plugins/Samples/Common/FullOrthancDataset.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/FullOrthancDataset.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/FullOrthancDataset.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/FullOrthancDataset.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/IDicomDataset.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/IDicomDataset.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/IOrthancConnection.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/IOrthancConnection.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/IOrthancConnection.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/IOrthancConnection.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancHttpConnection.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancHttpConnection.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancHttpConnection.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancHttpConnection.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPluginConnection.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancPluginConnection.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPluginConnection.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancPluginConnection.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,8 +33,9 @@ #include "OrthancPluginCppWrapper.h" +#include <boost/algorithm/string/predicate.hpp> +#include <boost/move/unique_ptr.hpp> #include <boost/thread.hpp> -#include <boost/algorithm/string/predicate.hpp> #include <json/reader.h> #include <json/writer.h> @@ -2159,6 +2160,109 @@ } } + + void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job) + { + static const char* KEY_SYNCHRONOUS = "Synchronous"; + static const char* KEY_ASYNCHRONOUS = "Asynchronous"; + static const char* KEY_PRIORITY = "Priority"; + + boost::movelib::unique_ptr<OrthancJob> protection(job); + + if (body.type() != Json::objectValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Expected a JSON object in the body"); +#else + LogError("Expected a JSON object in the body"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + + bool synchronous = true; + + if (body.isMember(KEY_SYNCHRONOUS)) + { + if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_SYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = body[KEY_SYNCHRONOUS].asBool(); + } + } + + if (body.isMember(KEY_ASYNCHRONOUS)) + { + if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_ASYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = !body[KEY_ASYNCHRONOUS].asBool(); + } + } + + int priority = 0; + + if (body.isMember(KEY_PRIORITY)) + { + if (body[KEY_PRIORITY].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_PRIORITY) + + "\" must be an integer"); +#else + LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + priority = !body[KEY_PRIORITY].asInt(); + } + } + + Json::Value result; + + if (synchronous) + { + OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority); + } + else + { + std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority); + + result = Json::objectValue; + result["ID"] = id; + result["Path"] = "/jobs/" + id; + } + + std::string s = result.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), + s.size(), "application/json"); + } + #endif @@ -2956,7 +3060,7 @@ } else { - std::auto_ptr<IChunkedRequestReader> reader(PostHandler(url, request)); + boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request)); if (reader.get() == NULL) { ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); @@ -2989,7 +3093,7 @@ } else { - std::auto_ptr<IChunkedRequestReader> reader(PutHandler(url, request)); + boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request)); if (reader.get() == NULL) { ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); @@ -3036,4 +3140,41 @@ } #endif } + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup( + OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid) + { + assert(target != NULL && + rawHandler != NULL); + + try + { + IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler); + *target = handler.Lookup(sopClassUid, sopInstanceUid); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + void IStorageCommitmentScpHandler::Destructor(void* rawHandler) + { + assert(rawHandler != NULL); + delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler); + } +#endif }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -103,6 +103,12 @@ # define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 0 #endif +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0) +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 1 +#else +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 +#endif + namespace OrthancPlugins @@ -778,6 +784,13 @@ static void SubmitAndWait(Json::Value& result, OrthancJob* job /* takes ownership */, int priority); + + // Submit a job from a POST on the REST API with the same + // conventions as in the Orthanc core (according to the + // "Synchronous" and "Priority" options) + static void SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job); }; #endif @@ -1093,4 +1106,26 @@ #endif } }; + + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + class IStorageCommitmentScpHandler : public boost::noncopyable + { + public: + virtual ~IStorageCommitmentScpHandler() + { + } + + virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid, + const std::string& sopInstanceUid) = 0; + + static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid); + + static void Destructor(void* rawHandler); + }; +#endif }
--- a/Plugins/Samples/Common/OrthancPluginException.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancPluginException.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPlugins.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/OrthancPlugins.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -20,13 +20,18 @@ link_libraries(dl rt pthread) endif() - include_directories(${SAMPLES_ROOT}/../Include/) - if (MSVC) - include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/) + if (MSVC_VERSION LESS 1600) + # Starting with Visual Studio >= 2010 (i.e. macro _MSC_VER >= + # 1600), Microsoft ships a standard-compliant <stdint.h> + # header. For earlier versions of Visual Studio, give access to a + # compatibility header. + # http://stackoverflow.com/a/70630/881731 + # https://en.wikibooks.org/wiki/C_Programming/C_Reference/stdint.h#External_links + include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/) + endif() endif() - add_definitions(-DHAS_ORTHANC_EXCEPTION=0)
--- a/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/SimplifiedOrthancDataset.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/Common/SimplifiedOrthancDataset.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ConnectivityChecks/CMakeLists.txt Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 2.8) + +project(ConnectivityChecks) + +SET(PLUGIN_NAME "connectivity-checks" CACHE STRING "Name of the plugin") +SET(PLUGIN_VERSION "mainline" CACHE STRING "Version of the plugin") + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../Resources/CMake/OrthancFrameworkParameters.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../Resources/CMake/OrthancFrameworkConfiguration.cmake) + +include(JavaScriptLibraries.cmake) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${PLUGIN_VERSION} ConnectivityChecks ConnectivityChecks.dll "Orthanc plugin to serve additional folders" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/ConnectivityChecks.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND ADDITIONAL_RESOURCES ${AUTOGENERATED_DIR}/ConnectivityChecks.rc) +endif() + +EmbedResources( + WEB_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/WebResources + LIBRARIES ${JAVASCRIPT_LIBS_DIR} + ) + +add_definitions( + -DHAS_ORTHANC_EXCEPTION=1 + -DORTHANC_ENABLE_LOGGING_PLUGIN=1 + -DORTHANC_PLUGIN_NAME="${PLUGIN_NAME}" + -DORTHANC_PLUGIN_VERSION="${PLUGIN_VERSION}" + ) + +include_directories( + ${ORTHANC_ROOT}/Plugins/Include/ + ) + +add_library(ConnectivityChecks SHARED + ${ADDITIONAL_RESOURCES} + ${AUTOGENERATED_SOURCES} + ${ORTHANC_CORE_SOURCES_DEPENDENCIES} + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/Logging.cpp + ${ORTHANC_ROOT}/Core/SystemToolbox.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + Plugin.cpp + ) + +set_target_properties( + ConnectivityChecks PROPERTIES + VERSION ${PLUGIN_VERSION} + SOVERSION ${PLUGIN_VERSION} + ) + +install( + TARGETS ConnectivityChecks + DESTINATION . + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ConnectivityChecks/JavaScriptLibraries.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,42 @@ +set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads") + +DownloadPackage( + "da0189f7c33bf9f652ea65401e0a3dc9" + "${BASE_URL}/dicom-web/bootstrap-4.3.1.zip" + "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1") + +DownloadPackage( + "8242afdc5bd44105d9dc9e6535315484" + "${BASE_URL}/dicom-web/vuejs-2.6.10.tar.gz" + "${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10") + +DownloadPackage( + "3e2b4e1522661f7fcf8ad49cb933296c" + "${BASE_URL}/dicom-web/axios-0.19.0.tar.gz" + "${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0") + +DownloadFile( + "220afd743d9e9643852e31a135a9f3ae" + "${BASE_URL}/jquery-3.4.1.min.js") + + +set(JAVASCRIPT_LIBS_DIR ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs) +file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR}) + +file(COPY + ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.js + ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.map + ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js + ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js.map + ${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10/dist/vue.min.js + ${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/jquery-3.4.1.min.js + DESTINATION + ${JAVASCRIPT_LIBS_DIR}/js + ) + +file(COPY + ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css + ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css.map + DESTINATION + ${JAVASCRIPT_LIBS_DIR}/css + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ConnectivityChecks/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,124 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 <EmbeddedResources.h> +#include <orthanc/OrthancCPlugin.h> + +#include "../../../Core/OrthancException.h" +#include "../../../Core/SystemToolbox.h" + +#define ROOT_URI "/connectivity-checks" + + +static OrthancPluginContext* context_ = NULL; + + +template <Orthanc::EmbeddedResources::DirectoryResourceId DIRECTORY> +static OrthancPluginErrorCode ServeStaticResource(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context_, output, "GET"); + return OrthancPluginErrorCode_Success; + } + + std::string path = "/" + std::string(request->groups[0]); + std::string mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path)); + + try + { + std::string s; + Orthanc::EmbeddedResources::GetDirectoryResource(s, DIRECTORY, path.c_str()); + + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime.c_str()); + } + catch (Orthanc::OrthancException&) + { + std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]); + OrthancPluginLogError(context_, s.c_str()); + OrthancPluginSendHttpStatusCode(context_, output, 404); + } + + return OrthancPluginErrorCode_Success; +} + + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) + { + context_ = c; + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(c) == 0) + { + char info[256]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + c->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + /* Register the callbacks */ + OrthancPluginSetDescription(context_, "Utilities to check connectivity to DICOM modalities, DICOMweb servers and Orthanc peers."); + OrthancPluginSetRootUri(context_, ROOT_URI "/app/index.html"); + OrthancPluginRegisterRestCallback(context_, ROOT_URI "/libs/(.*)", ServeStaticResource<Orthanc::EmbeddedResources::LIBRARIES>); + OrthancPluginRegisterRestCallback(context_, ROOT_URI "/app/(.*)", ServeStaticResource<Orthanc::EmbeddedResources::WEB_RESOURCES>); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return ORTHANC_PLUGIN_NAME; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return ORTHANC_PLUGIN_VERSION; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ConnectivityChecks/WebResources/app.js Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,145 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/>. + **/ + + +new Vue({ + el: '#app', + data: { + dicomNodes: {}, + peers: [], + canTestPeers: false, + dicomWebServers: [] + }, + methods: { + toggle: function (todo) { + todo.done = !todo.done + }, + + testDicomModalities: function () { + console.log('testing DICOM modalities'); + axios + .get('../../modalities?expand') + .then(response => { + this.dicomNodes = response.data; + for (let alias of Object.keys(this.dicomNodes)) { + this.dicomNodes[alias]['alias'] = alias; + this.dicomNodes[alias]['status'] = 'testing'; + axios + .post('../../modalities/' + alias + '/echo') + .then(response => { + this.dicomNodes[alias]['status'] = 'ok'; + this.$forceUpdate(); + }) + .catch(response => { + this.dicomNodes[alias]['status'] = 'ko'; + this.$forceUpdate(); + }) + } + }) + }, + + testOrthancPeers: function () { + console.log('testing Orthanc peers'); + axios + .get('../../peers?expand') + .then(response => { + this.peers = response.data; + for (let alias of Object.keys(this.peers)) { + this.peers[alias]['alias'] = alias; + + if (this.canTestPeers) { + this.peers[alias]['status'] = 'testing'; + axios + .get('../../peers/' + alias + '/system') // introduced in ApiVersion 5 only ! + .then(response => { + this.peers[alias]['status'] = 'ok'; + this.$forceUpdate(); + }) + .catch(response => { + this.peers[alias]['status'] = 'ko'; + this.$forceUpdate(); + }) + } + else { + this.peers[alias]['status'] = 'unknown'; + this.$forceUpdate(); + } + } + }) + }, + + testDicomWebServers: function () { + console.log('testing Dicom-web servers'); + axios + .get('../../dicom-web/servers?expand') + .then(response => { + this.dicomWebServers = response.data; + for (let alias of Object.keys(this.dicomWebServers)) { + this.dicomWebServers[alias]['alias'] = alias; + this.dicomWebServers[alias]['status'] = 'testing'; + + // perform a dummy qido-rs to test the connectivity + axios + .post('../../dicom-web/servers/' + alias + '/qido', { + 'Uri' : '/studies', + 'Arguments' : { + '00100010' : 'CONNECTIVITY^CHECKS' + } + }) + .then(response => { + this.dicomWebServers[alias]['status'] = 'ok'; + this.$forceUpdate(); + }) + .catch(response => { + this.dicomWebServers[alias]['status'] = 'ko'; + this.$forceUpdate(); + }) + } + }) + }, + + }, + computed: { + }, + mounted() { + axios + .get('../../system') + .then(response => { + this.canTestPeers = response.data.ApiVersion >= 5; + this.testDicomModalities(); + if (this.canTestPeers) { + this.testOrthancPeers(); + } + this.testDicomWebServers(); + }) + } +})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ConnectivityChecks/WebResources/index.html Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + + <link rel="stylesheet" href="../libs/css/bootstrap.min.css"> + + <title>Orthanc Connectivity checks</title> + <link rel="stylesheet" href="style.css" type="text/css"> + </head> + + <body> + <div id="app" class="container-fluid"> + <h2>DICOM nodes</h2> + <table class="table"> + <thead> + <tr> + <th scope="col">Alias</th> + <th scope="col">AET</th> + <th scope="col">Host</th> + <th scope="col">Port</th> + <th scope="col">Status</th> + </tr> + </thead> + <tbody> + <tr v-for="node in dicomNodes"> + <th scope="row">{{node.alias}}</th> + <td>{{node.AET}}</td> + <td>{{node.Host}}</td> + <td>{{node.Port}}</td> + <td v-if="node.status=='ok'" class="connected">Connected</td> + <td v-if="node.status=='ko'" class="disconnected">Disconnected</td> + <td v-if="node.status=='testing'"> + <div class="spinner-border" role="status"> + <span class="sr-only">Testing...</span> + </div> + </td> + </tr> + </tbody> + </table> + + <h2>Orthanc peers</h2> + <table class="table" v-if="canTestPeers"> + <thead> + <tr> + <th scope="col">Alias</th> + <th scope="col">Url</th> + <th scope="col">Status</th> + </tr> + </thead> + <tbody> + <tr v-for="node in peers"> + <th scope="row">{{node.alias}}</th> + <td>{{node.Url}}</td> + <td v-if="node.status=='ok'" class="connected">Connected</td> + <td v-if="node.status=='ko'" class="disconnected">Disconnected</td> + <td v-if="node.status=='unknown'" class="unknown"> + Can not test the peers connectivity with this version of Orthanc + </td> + <td v-if="node.status=='testing'"> + <div class="spinner-border" role="status"> + <span class="sr-only">Testing...</span> + </div> + </td> + </tr> + </tbody> + </table> + + <h2>DicomWeb servers</h2> + <table class="table"> + <thead> + <tr> + <th scope="col">Alias</th> + <th scope="col">Url</th> + <th scope="col">Status</th> + </tr> + </thead> + <tbody> + <tr v-for="node in dicomWebServers"> + <th scope="row">{{node.alias}}</th> + <td>{{node.Url}}</td> + <td v-if="node.status=='ok'" class="connected">Connected</td> + <td v-if="node.status=='ko'" class="disconnected">Disconnected</td> + <td v-if="node.status=='testing'"> + <div class="spinner-border" role="status"> + <span class="sr-only">Testing...</span> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <script src="../libs/js/jquery-3.4.1.min.js" type="text/javascript"></script> + <script src="../libs/js/bootstrap.min.js" type="text/javascript"></script> + <script src="../libs/js/axios.min.js" type="text/javascript"></script> + <script src="../libs/js/vue.min.js" type="text/javascript"></script> + <script src="app.js" type="text/javascript"></script> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ConnectivityChecks/WebResources/style.css Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,13 @@ +.connected { + background-color: darkgreen; + color: white; +} + +.disconnected { + background-color: darkred; + color: white; +} + +.unknown { + background-color: gold; +}
--- a/Plugins/Samples/CustomImageDecoder/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/CustomImageDecoder/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -21,6 +21,7 @@ #include "GdcmDecoderCache.h" +#include "../../../Core/Compatibility.h" #include "OrthancImageWrapper.h" namespace OrthancPlugins @@ -83,13 +84,13 @@ } // This is not the same image - std::auto_ptr<GdcmImageDecoder> decoder(new GdcmImageDecoder(dicom, size)); - std::auto_ptr<OrthancImageWrapper> image(new OrthancImageWrapper(context, decoder->Decode(context, frameIndex))); + std::unique_ptr<GdcmImageDecoder> decoder(new GdcmImageDecoder(dicom, size)); + std::unique_ptr<OrthancImageWrapper> image(new OrthancImageWrapper(context, decoder->Decode(context, frameIndex))); { // Cache the newly created decoder for further use boost::mutex::scoped_lock lock(mutex_); - decoder_ = decoder; + decoder_.reset(decoder.release()); size_ = size; md5_ = md5; }
--- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -21,6 +21,7 @@ #pragma once +#include "../../../Core/Compatibility.h" #include "GdcmImageDecoder.h" #include "OrthancImageWrapper.h" @@ -33,7 +34,7 @@ { private: boost::mutex mutex_; - std::auto_ptr<OrthancPlugins::GdcmImageDecoder> decoder_; + std::unique_ptr<OrthancPlugins::GdcmImageDecoder> decoder_; size_t size_; std::string md5_;
--- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -21,6 +21,7 @@ #include "GdcmImageDecoder.h" +#include "../../../Core/Compatibility.h" #include "OrthancImageWrapper.h" #include <gdcmImageReader.h> @@ -40,9 +41,9 @@ size_t size_; gdcm::ImageReader reader_; - std::auto_ptr<gdcm::ImageApplyLookupTable> lut_; - std::auto_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_; - std::auto_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_; + std::unique_ptr<gdcm::ImageApplyLookupTable> lut_; + std::unique_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_; + std::unique_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_; std::string decoded_; PImpl(const void* dicom,
--- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/GdcmDecoder/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -19,6 +19,7 @@ **/ +#include "../../../Core/Compatibility.h" #include "GdcmDecoderCache.h" #include "OrthancImageWrapper.h" @@ -35,7 +36,7 @@ { try { - std::auto_ptr<OrthancPlugins::OrthancImageWrapper> image; + std::unique_ptr<OrthancPlugins::OrthancImageWrapper> image; #if 0 // Do not use the cache
--- a/Plugins/Samples/ModalityWorklists/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/ModalityWorklists/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -19,6 +19,7 @@ **/ +#include "../../../Core/Compatibility.h" #include "../Common/OrthancPluginCppWrapper.h" #include <boost/filesystem.hpp> @@ -142,7 +143,7 @@ try { // Construct an object to match the worklists in the database against the C-Find query - std::auto_ptr<OrthancPlugins::FindMatcher> matcher(CreateMatcher(query, issuerAet)); + std::unique_ptr<OrthancPlugins::FindMatcher> matcher(CreateMatcher(query, issuerAet)); // Loop over the regular files in the database folder namespace fs = boost::filesystem;
--- a/Plugins/Samples/ModalityWorklists/WorklistsDatabase/Generate.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/ModalityWorklists/WorklistsDatabase/Generate.py Thu Mar 19 11:48:30 2020 +0100 @@ -6,7 +6,7 @@ SOURCE = '/home/jodogne/Downloads/dcmtk-3.6.0/dcmwlm/data/wlistdb/OFFIS/' TARGET = os.path.abspath(os.path.dirname(__file__)) -for f in os.listdir(SOURCE): +for f in sorted(os.listdir(SOURCE)): ext = os.path.splitext(f) if ext[1].lower() == '.dump':
--- a/Plugins/Samples/ServeFolders/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/ServeFolders/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/StorageArea/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/StorageArea/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/StorageCommitmentScp/CMakeLists.txt Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8) + +project(StorageCommitmentScp) + +SET(PLUGIN_VERSION "0.0" CACHE STRING "Version of the plugin") +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") + +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost") + +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) + +add_library(StorageCommitmentScp SHARED + Plugin.cpp + ../Common/OrthancPluginCppWrapper.cpp + ${JSONCPP_SOURCES} + ${BOOST_SOURCES} + ) + +message("Setting the version of the plugin to ${PLUGIN_VERSION}") +add_definitions( + -DPLUGIN_VERSION="${PLUGIN_VERSION}" + ) + +set_target_properties(StorageCommitmentScp PROPERTIES + VERSION ${PLUGIN_VERSION} + SOVERSION ${PLUGIN_VERSION}) + +install( + TARGETS StorageCommitmentScp + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/StorageCommitmentScp/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,116 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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. + * + * 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 "../Common/OrthancPluginCppWrapper.h" + +#include <json/value.h> +#include <json/reader.h> + + + +class StorageCommitmentSample : public OrthancPlugins::IStorageCommitmentScpHandler +{ +private: + int count_; + +public: + StorageCommitmentSample() : count_(0) + { + } + + virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid, + const std::string& sopInstanceUid) + { + printf("?? [%s] [%s]\n", sopClassUid.c_str(), sopInstanceUid.c_str()); + if (count_++ % 2 == 0) + return OrthancPluginStorageCommitmentFailureReason_Success; + else + return OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance; + } +}; + + +static OrthancPluginErrorCode StorageCommitmentScp(void** handler /* out */, + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstanceUids, + uint32_t countInstances, + const char* remoteAet, + const char* calledAet) +{ + /*std::string s; + OrthancPlugins::RestApiPost(s, "/jobs/" + std::string(jobId) + "/pause", NULL, 0, false);*/ + + printf("[%s] [%s] [%s] [%s]\n", jobId, transactionUid, remoteAet, calledAet); + + for (uint32_t i = 0; i < countInstances; i++) + { + printf("++ [%s] [%s]\n", sopClassUids[i], sopInstanceUids[i]); + } + + *handler = new StorageCommitmentSample; + return OrthancPluginErrorCode_Success; +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) + { + OrthancPlugins::SetGlobalContext(c); + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(c) == 0) + { + OrthancPlugins::ReportMinimalOrthancVersion(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + return -1; + } + + OrthancPluginSetDescription(c, "Sample storage commitment SCP plugin."); + + OrthancPluginRegisterStorageCommitmentScpCallback( + c, StorageCommitmentScp, + OrthancPlugins::IStorageCommitmentScpHandler::Destructor, + OrthancPlugins::IStorageCommitmentScpHandler::Lookup); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "storage-commitment-scp"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return PLUGIN_VERSION; + } +}
--- a/Plugins/Samples/WebSkeleton/Configuration.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/WebSkeleton/Configuration.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py Thu Mar 19 11:48:30 2020 +0100 @@ -1,7 +1,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -74,6 +74,8 @@ # The resource is a directory: Recursively explore its files content = {} for root, dirs, files in os.walk(pathName): + dirs.sort() + files.sort() base = os.path.relpath(root, pathName) for f in files: if f.find('~') == -1: # Ignore Emacs backup files
--- a/Plugins/Samples/WebSkeleton/Framework/Framework.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/Framework.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -1,7 +1,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/CMake/BoostConfiguration.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/BoostConfiguration.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -12,7 +12,7 @@ endif() list(APPEND ORTHANC_BOOST_COMPONENTS filesystem thread system date_time regex) - find_package(Boost COMPONENTS "${ORTHANC_BOOST_COMPONENTS}") + find_package(Boost COMPONENTS ${ORTHANC_BOOST_COMPONENTS}) if (NOT Boost_FOUND) foreach (item ${ORTHANC_BOOST_COMPONENTS}) @@ -30,9 +30,20 @@ message(FATAL_ERROR "Unable to locate Boost on this system") endif() + + # Patch by xnox to fix issue #166 (CMake find_boost version is now + # broken with newer boost/cmake) + # https://bitbucket.org/sjodogne/orthanc/issues/166/ + if (POLICY CMP0093) + set(BOOST144 1.44) + else() + set(BOOST144 104400) + endif() + + # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm - if (${Boost_VERSION} LESS 104400) + if (${Boost_VERSION} LESS ${BOOST144}) add_definitions( -DBOOST_HAS_FILESYSTEM_V3=0 ) @@ -107,6 +118,13 @@ -DBOOST_REGEX_NO_LIB -DBOOST_SYSTEM_NO_LIB -DBOOST_LOCALE_NO_LIB + + # In static builds, explicitly prevent Boost from using the system + # locale in lexical casts. This is notably important if + # "boost::lexical_cast<double>()" is applied to strings containing + # "," instead of "." as decimal separators. Check out function + # "OrthancStone::LinearAlgebra::ParseVector()". + -DBOOST_LEXICAL_CAST_ASSUME_C_LOCALE ) set(BOOST_SOURCES
--- a/Resources/CMake/CivetwebConfiguration.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/CivetwebConfiguration.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -30,6 +30,12 @@ ${CIVETWEB_SOURCES_DIR}/src/civetweb.c ) + # New in Orthanc 1.6.0: Enable support of compression in civetweb + set_source_files_properties( + ${CIVETWEB_SOURCES} + PROPERTIES COMPILE_DEFINITIONS + "USE_ZLIB=1") + if (ENABLE_SSL) add_definitions( -DNO_SSL_DL=1
--- a/Resources/CMake/Compiler.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/Compiler.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -7,6 +7,15 @@ SET(STANDALONE_BUILD ON) endif() + +if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + # Cache the environment variables "LSB_CC" and "LSB_CXX" for further + # use by "ExternalProject" in CMake + SET(CMAKE_LSB_CC $ENV{LSB_CC} CACHE STRING "") + SET(CMAKE_LSB_CXX $ENV{LSB_CXX} CACHE STRING "") +endif() + + if (CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long") @@ -163,15 +172,21 @@ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}") - # This is a patch for MinGW64 - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") + if (DYNAMIC_MINGW_STDLIB) + else() + # This is a patch for MinGW64 + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") + endif() CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD) if (HAVE_WIN_PTHREAD) - # This line is necessary to compile with recent versions of MinGW, - # otherwise "libwinpthread-1.dll" is not statically linked. - SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic") + if (DYNAMIC_MINGW_STDLIB) + else() + # This line is necessary to compile with recent versions of MinGW, + # otherwise "libwinpthread-1.dll" is not statically linked. + SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic") + endif() add_definitions(-DHAVE_WIN_PTHREAD=1) else() add_definitions(-DHAVE_WIN_PTHREAD=0) @@ -193,12 +208,15 @@ # zero (and similar conditions like integer overflows) are # encountered: The "clamp" mode avoids throwing errors, as they # cannot be properly catched by "try {} catch (...)" constructions. - if (EMSCRIPTEN_SET_LLVM_WASM_BACKEND) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") - else() - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]' -s BINARYEN_TRAP_MODE='\"clamp\"'") + # Setting this option to "ON" fixes error: "shared:ERROR: + # BINARYEN_TRAP_MODE is not supported by the LLVM wasm backend" if + # using the "upstream" backend of Emscripten. + if (NOT EMSCRIPTEN_SET_LLVM_WASM_BACKEND) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s BINARYEN_TRAP_MODE='\"clamp\"'") endif() + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + elseif (CMAKE_SYSTEM_NAME STREQUAL "Android") else()
--- a/Resources/CMake/DcmtkConfiguration.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/DcmtkConfiguration.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -9,6 +9,8 @@ include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.2.cmake) elseif (DCMTK_STATIC_VERSION STREQUAL "3.6.4") include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.4.cmake) + elseif (DCMTK_STATIC_VERSION STREQUAL "3.6.5") + include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.5.cmake) else() message(FATAL_ERROR "Unsupported version of DCMTK: ${DCMTK_STATIC_VERSION}") endif() @@ -24,6 +26,7 @@ LIST(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc ) if (ENABLE_DCMTK_NETWORKING) @@ -48,6 +51,12 @@ list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/ddpiimpl.cc + # Solves linking problem in WebAssembly: "wasm-ld: error: + # duplicate symbol: jaritab" (modification in Orthanc 1.5.9) + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8/jaricom.c + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12/jaricom.c + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg24/jaricom.c + # Disable support for encoding JPEG (modification in Orthanc 1.0.1) ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc
--- a/Resources/CMake/DcmtkConfigurationStatic-3.6.2.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/DcmtkConfigurationStatic-3.6.2.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -12,7 +12,12 @@ set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/) set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/) -set(DCMTK_WITH_THREADS ON) + +if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + set(DCMTK_WITH_THREADS OFF) # Disable thread support in wasm/asm.js +else() + set(DCMTK_WITH_THREADS ON) +endif() add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1) @@ -31,7 +36,7 @@ message("Applying patch to detect mathematic primitives in DCMTK 3.6.2 with C++11") execute_process( COMMAND ${PATCH_EXECUTABLE} -p0 -N -i - ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2-cmath.patch + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2.patch WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) @@ -39,6 +44,11 @@ if (Failure) message(FATAL_ERROR "Error while patching a file") endif() + + configure_file( + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-dcdict_orthanc.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc + COPYONLY) else() message("The patches for DCMTK have already been applied") endif() @@ -72,18 +82,21 @@ SET(DCMTK_ENABLE_CHARSET_CONVERSION "iconv" CACHE STRING "") SET(HAVE_SYS_GETTID 0 CACHE INTERNAL "") - execute_process( - COMMAND ${PATCH_EXECUTABLE} -p0 -N -i - ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2-linux-standard-base.patch - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) + if (FirstRun) + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2-linux-standard-base.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) - if (FirstRun AND Failure) - message(FATAL_ERROR "Error while patching a file") + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() endif() endif() + SET(DCMTK_SOURCE_DIR ${DCMTK_SOURCES_DIR}) include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) @@ -163,19 +176,6 @@ endif() -if (ORTHANC_SANDBOXED) - configure_file( - ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.h - ${DCMTK_SOURCES_DIR}/dcmdata/include/dcmtk/dcmdata/dcdict.h - COPYONLY) - - configure_file( - ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict.cc - COPYONLY) -endif() - - #set_source_files_properties(${DCMTK_SOURCES} # PROPERTIES COMPILE_DEFINITIONS # "PACKAGE_VERSION=\"${DCMTK_PACKAGE_VERSION}\";PACKAGE_VERSION_NUMBER=\"${DCMTK_VERSION_NUMBER}\"")
--- a/Resources/CMake/DcmtkConfigurationStatic-3.6.4.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/DcmtkConfigurationStatic-3.6.4.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -12,7 +12,12 @@ set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/) set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/) -set(DCMTK_WITH_THREADS ON) + +if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + set(DCMTK_WITH_THREADS OFF) # Disable thread support in wasm/asm.js +else() + set(DCMTK_WITH_THREADS ON) +endif() add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1) @@ -25,16 +30,25 @@ DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}") -# Apply the patches -execute_process( - COMMAND ${PATCH_EXECUTABLE} -p0 -N -i - ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.4.patch - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) +if (FirstRun) + # Apply the patches + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.4.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) -if (FirstRun AND Failure) - message(FATAL_ERROR "Error while patching files") + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() + + configure_file( + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-dcdict_orthanc.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc + COPYONLY) +else() + message("The patches for DCMTK have already been applied") endif() @@ -152,19 +166,6 @@ endif() -if (ORTHANC_SANDBOXED) - configure_file( - ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.h - ${DCMTK_SOURCES_DIR}/dcmdata/include/dcmtk/dcmdata/dcdict.h - COPYONLY) - - configure_file( - ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict.cc - COPYONLY) -endif() - - list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/DcmtkConfigurationStatic-3.6.5.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,210 @@ +SET(DCMTK_VERSION_NUMBER 365) +SET(DCMTK_PACKAGE_VERSION "3.6.5") +SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.5) +SET(DCMTK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dcmtk-3.6.5.tar.gz") +SET(DCMTK_MD5 "e19707f64ee5695c496b9c1e48e39d07") + +macro(DCMTK_UNSET) +endmacro() + +macro(DCMTK_UNSET_CACHE) +endmacro() + +set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/) +set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/) + +if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + set(DCMTK_WITH_THREADS OFF) # Disable thread support in wasm/asm.js +else() + set(DCMTK_WITH_THREADS ON) +endif() + +add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1) + +if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}") + set(FirstRun OFF) +else() + set(FirstRun ON) +endif() + +DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}") + + +if (FirstRun) + # Apply the patches + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.5.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() + + configure_file( + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-dcdict_orthanc.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc + COPYONLY) +else() + message("The patches for DCMTK have already been applied") +endif() + + +include_directories( + ${DCMTK_SOURCES_DIR}/dcmiod/include + ) + + +# C_CHAR_UNSIGNED *must* be set before calling "GenerateDCMTKConfigure.cmake" +IF (CMAKE_CROSSCOMPILING) + if (CMAKE_COMPILER_IS_GNUCXX AND + CMAKE_SYSTEM_NAME STREQUAL "Windows") # MinGW + SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") + + elseif(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") # WebAssembly or asm.js + + # Check out "../WebAssembly/ArithmeticTests/" to regenerate the + # "arith.h" file + configure_file( + ${ORTHANC_ROOT}/Resources/WebAssembly/arith.h + ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/arith.h + COPYONLY) + + UNSET(C_CHAR_UNSIGNED CACHE) + SET(C_CHAR_UNSIGNED 0 CACHE INTERNAL "") + + else() + message(FATAL_ERROR "Support your platform here") + endif() +ENDIF() + + +if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + SET(DCMTK_ENABLE_CHARSET_CONVERSION "iconv" CACHE STRING "") + SET(HAVE_SYS_GETTID 0 CACHE INTERNAL "") +endif() + + +SET(DCMTK_SOURCE_DIR ${DCMTK_SOURCES_DIR}) +include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) +include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) + + +if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") # WebAssembly or + # asm.js The macros below are not properly discovered by DCMTK + # when using WebAssembly. Check out "../WebAssembly/arith.h" for + # how we produced these values. This step MUST be after + # "GenerateDCMTKConfigure" and before the generation of + # "osconfig.h". + UNSET(SIZEOF_VOID_P CACHE) + UNSET(SIZEOF_CHAR CACHE) + UNSET(SIZEOF_DOUBLE CACHE) + UNSET(SIZEOF_FLOAT CACHE) + UNSET(SIZEOF_INT CACHE) + UNSET(SIZEOF_LONG CACHE) + UNSET(SIZEOF_SHORT CACHE) + UNSET(SIZEOF_VOID_P CACHE) + + SET(SIZEOF_VOID_P 4 CACHE INTERNAL "") + SET(SIZEOF_CHAR 1 CACHE INTERNAL "") + SET(SIZEOF_DOUBLE 8 CACHE INTERNAL "") + SET(SIZEOF_FLOAT 4 CACHE INTERNAL "") + SET(SIZEOF_INT 4 CACHE INTERNAL "") + SET(SIZEOF_LONG 4 CACHE INTERNAL "") + SET(SIZEOF_SHORT 2 CACHE INTERNAL "") + SET(SIZEOF_VOID_P 4 CACHE INTERNAL "") +endif() + + +set(DCMTK_PACKAGE_VERSION_SUFFIX "") +set(DCMTK_PACKAGE_VERSION_NUMBER ${DCMTK_VERSION_NUMBER}) + +CONFIGURE_FILE( + ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in + ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + link_libraries(netapi32) # For NetWkstaUserGetInfo@12 + link_libraries(iphlpapi) # For GetAdaptersInfo@8 + + # Configure Wine if cross-compiling for Windows + if (CMAKE_COMPILER_IS_GNUCXX) + include(${DCMTK_SOURCES_DIR}/CMake/dcmtkUseWine.cmake) + FIND_PROGRAM(WINE_WINE_PROGRAM wine) + FIND_PROGRAM(WINE_WINEPATH_PROGRAM winepath) + list(APPEND DCMTK_TRY_COMPILE_REQUIRED_CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS=-static") + endif() +endif() + +# This step must be after the generation of "osconfig.h" +if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + INSPECT_FUNDAMENTAL_ARITHMETIC_TYPES() +endif() + + +# Source for the logging facility of DCMTK +AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/clfsap.cc + ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc + ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc + ) + +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc + ${DCMTK_SOURCES_DIR}/oflog/libsrc/clfsap.cc + ) +endif() + + +list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc + ) + + +# Starting with DCMTK 3.6.2, the Nagle algorithm is not disabled by +# default since this does not seem to be appropriate (anymore) for +# most modern operating systems. In order to change this default, the +# environment variable NO_TCPDELAY can be set to "1" (see envvars.txt +# for details). Alternatively, the macro DISABLE_NAGLE_ALGORITHM can +# be defined to change this setting at compilation time (see +# macros.txt for details). +# https://forum.dcmtk.org/viewtopic.php?t=4632 +add_definitions( + -DDISABLE_NAGLE_ALGORITHM=1 + ) + + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + # For compatibility with Windows XP, avoid using fiber-local-storage + # in log4cplus, but use thread-local-storage instead. Otherwise, + # Windows XP complains about missing "FlsGetValue()" in KERNEL32.dll + add_definitions( + -DDCMTK_LOG4CPLUS_AVOID_WIN32_FLS + ) + + if (CMAKE_COMPILER_IS_GNUCXX OR # MinGW + "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # MSVC for 32bit (*) + + # (*) With multithreaded logging enabled, Visual Studio 2008 fails + # with error: ".\dcmtk-3.6.5\oflog\libsrc\globinit.cc(422) : error + # C2664: 'dcmtk::log4cplus::thread::impl::tls_init' : cannot + # convert parameter 1 from 'void (__stdcall *)(void *)' to + # 'dcmtk::log4cplus::thread::impl::tls_init_cleanup_func_type'" + # None of the functions with this name in scope match the target type + + add_definitions( + -DDCMTK_LOG4CPLUS_SINGLE_THREADED + ) + endif() +endif()
--- a/Resources/CMake/JsonCppConfiguration.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/JsonCppConfiguration.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -2,9 +2,9 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP) if (USE_LEGACY_JSONCPP) - set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.6) - set(JSONCPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/jsoncpp-0.10.6.tar.gz") - set(JSONCPP_MD5 "13d1991d79697df8cadbc25c93e37c83") + set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.7) + set(JSONCPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/jsoncpp-0.10.7.tar.gz") + set(JSONCPP_MD5 "3a8072ca6a1fa9cbaf7715ae625f134f") add_definitions(-DORTHANC_LEGACY_JSONCPP=1) else() set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-1.8.4) @@ -78,9 +78,11 @@ # https://gitlab.kitware.com/third-party/jsoncpp/commit/56df2068470241f9043b676bfae415ed62a0c172 add_definitions(-DJSONCPP_DEPRECATED_STACK_LIMIT=5000) - if (CMAKE_COMPILER_IS_GNUCXX OR - "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - message("Switching to C++11 standard in gcc/clang, as version of JsonCpp is >= 1.0.0") + if (CMAKE_COMPILER_IS_GNUCXX) + message("Switching to C++11 standard in gcc, as version of JsonCpp is >= 1.0.0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wno-deprecated-declarations") + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + message("Switching to C++11 standard in clang, as version of JsonCpp is >= 1.0.0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations") endif() endif()
--- a/Resources/CMake/LibCurlConfiguration.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/LibCurlConfiguration.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -60,7 +60,7 @@ ) endif() - if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/curl_config.h") + if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h") #file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "") file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h "#include \"../vauth.h\"\n")
--- a/Resources/CMake/OpenSslConfiguration.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/OpenSslConfiguration.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -1,335 +1,10 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL) - SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2p) - SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.0.2p.tar.gz") - SET(OPENSSL_MD5 "ac5eb30bf5798aa14b1ae6d0e7da58df") - - if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}") - set(FirstRun OFF) + if (OPENSSL_STATIC_VERSION STREQUAL "1.0.2") + include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfigurationStatic-1.0.2.cmake) + elseif (OPENSSL_STATIC_VERSION STREQUAL "1.1.1") + include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfigurationStatic-1.1.1.cmake) else() - set(FirstRun ON) - endif() - - DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}") - - if (FirstRun) - file(MAKE_DIRECTORY ${OPENSSL_SOURCES_DIR}/include/openssl) - - foreach(header - ${OPENSSL_SOURCES_DIR}/crypto/aes/aes.h - ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1.h - ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1_mac.h - ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1t.h - ${OPENSSL_SOURCES_DIR}/crypto/bf/blowfish.h - ${OPENSSL_SOURCES_DIR}/crypto/bio/bio.h - ${OPENSSL_SOURCES_DIR}/crypto/bn/bn.h - ${OPENSSL_SOURCES_DIR}/crypto/buffer/buffer.h - ${OPENSSL_SOURCES_DIR}/crypto/camellia/camellia.h - ${OPENSSL_SOURCES_DIR}/crypto/cast/cast.h - ${OPENSSL_SOURCES_DIR}/crypto/cmac/cmac.h - ${OPENSSL_SOURCES_DIR}/crypto/cms/cms.h - ${OPENSSL_SOURCES_DIR}/crypto/comp/comp.h - ${OPENSSL_SOURCES_DIR}/crypto/conf/conf.h - ${OPENSSL_SOURCES_DIR}/crypto/conf/conf_api.h - ${OPENSSL_SOURCES_DIR}/crypto/crypto.h - ${OPENSSL_SOURCES_DIR}/crypto/des/des.h - ${OPENSSL_SOURCES_DIR}/crypto/des/des_old.h - ${OPENSSL_SOURCES_DIR}/crypto/dh/dh.h - ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsa.h - ${OPENSSL_SOURCES_DIR}/crypto/dso/dso.h - ${OPENSSL_SOURCES_DIR}/crypto/ebcdic.h - ${OPENSSL_SOURCES_DIR}/crypto/ec/ec.h - ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdh.h - ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsa.h - ${OPENSSL_SOURCES_DIR}/crypto/engine/engine.h - ${OPENSSL_SOURCES_DIR}/crypto/err/err.h - ${OPENSSL_SOURCES_DIR}/crypto/evp/evp.h - ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmac.h - ${OPENSSL_SOURCES_DIR}/crypto/idea/idea.h - ${OPENSSL_SOURCES_DIR}/crypto/jpake/jpake.h - ${OPENSSL_SOURCES_DIR}/crypto/krb5/krb5_asn.h - ${OPENSSL_SOURCES_DIR}/crypto/lhash/lhash.h - ${OPENSSL_SOURCES_DIR}/crypto/md2/md2.h - ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.h - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.h - ${OPENSSL_SOURCES_DIR}/crypto/mdc2/mdc2.h - ${OPENSSL_SOURCES_DIR}/crypto/modes/modes.h - ${OPENSSL_SOURCES_DIR}/crypto/objects/obj_mac.h - ${OPENSSL_SOURCES_DIR}/crypto/objects/objects.h - ${OPENSSL_SOURCES_DIR}/crypto/ocsp/ocsp.h - ${OPENSSL_SOURCES_DIR}/crypto/opensslconf.h - ${OPENSSL_SOURCES_DIR}/crypto/opensslv.h - ${OPENSSL_SOURCES_DIR}/crypto/ossl_typ.h - ${OPENSSL_SOURCES_DIR}/crypto/pem/pem.h - ${OPENSSL_SOURCES_DIR}/crypto/pem/pem2.h - ${OPENSSL_SOURCES_DIR}/crypto/pkcs12/pkcs12.h - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pkcs7.h - ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pqueue.h - ${OPENSSL_SOURCES_DIR}/crypto/rand/rand.h - ${OPENSSL_SOURCES_DIR}/crypto/rc2/rc2.h - ${OPENSSL_SOURCES_DIR}/crypto/rc4/rc4.h - ${OPENSSL_SOURCES_DIR}/crypto/rc5/rc5.h - ${OPENSSL_SOURCES_DIR}/crypto/ripemd/ripemd.h - ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa.h - ${OPENSSL_SOURCES_DIR}/crypto/seed/seed.h - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.h - ${OPENSSL_SOURCES_DIR}/crypto/srp/srp.h - ${OPENSSL_SOURCES_DIR}/crypto/stack/safestack.h - ${OPENSSL_SOURCES_DIR}/crypto/stack/stack.h - ${OPENSSL_SOURCES_DIR}/crypto/store/store.h - ${OPENSSL_SOURCES_DIR}/crypto/symhacks.h - ${OPENSSL_SOURCES_DIR}/crypto/ts/ts.h - ${OPENSSL_SOURCES_DIR}/crypto/txt_db/txt_db.h - ${OPENSSL_SOURCES_DIR}/crypto/ui/ui.h - ${OPENSSL_SOURCES_DIR}/crypto/ui/ui_compat.h - ${OPENSSL_SOURCES_DIR}/crypto/whrlpool/whrlpool.h - ${OPENSSL_SOURCES_DIR}/crypto/x509/x509.h - ${OPENSSL_SOURCES_DIR}/crypto/x509/x509_vfy.h - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/x509v3.h - ${OPENSSL_SOURCES_DIR}/e_os2.h - ${OPENSSL_SOURCES_DIR}/ssl/dtls1.h - ${OPENSSL_SOURCES_DIR}/ssl/kssl.h - ${OPENSSL_SOURCES_DIR}/ssl/srtp.h - ${OPENSSL_SOURCES_DIR}/ssl/ssl.h - ${OPENSSL_SOURCES_DIR}/ssl/ssl2.h - ${OPENSSL_SOURCES_DIR}/ssl/ssl23.h - ${OPENSSL_SOURCES_DIR}/ssl/ssl3.h - ${OPENSSL_SOURCES_DIR}/ssl/tls1.h - ) - file(COPY ${header} DESTINATION ${OPENSSL_SOURCES_DIR}/include/openssl) - endforeach() - - file(RENAME - ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h - ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2_source.h) - - # The following patch of "e_os2.h" prevents from building OpenSSL - # as a DLL under Windows. Otherwise, symbols have inconsistent - # linkage if ${OPENSSL_SOURCES} is used to create a DLL (notably - # if building an Orthanc plugin such as MySQL). - file(WRITE ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h " -#include \"e_os2_source.h\" -#if defined(_WIN32) -# undef OPENSSL_EXPORT -# undef OPENSSL_IMPORT -# undef OPENSSL_EXTERN -# undef OPENSSL_GLOBAL -# define OPENSSL_EXPORT -# define OPENSSL_IMPORT -# define OPENSSL_EXTERN extern -# define OPENSSL_GLOBAL -#endif -") - endif() - - add_definitions( - -DOPENSSL_THREADS - -DOPENSSL_IA32_SSE2 - -DOPENSSL_NO_ASM - -DOPENSSL_NO_DYNAMIC_ENGINE - -DNO_WINDOWS_BRAINDEATH - - -DOPENSSL_NO_BF - -DOPENSSL_NO_CAMELLIA - -DOPENSSL_NO_CAST - -DOPENSSL_NO_EC_NISTP_64_GCC_128 - -DOPENSSL_NO_GMP - -DOPENSSL_NO_GOST - -DOPENSSL_NO_HW - -DOPENSSL_NO_JPAKE - -DOPENSSL_NO_IDEA - -DOPENSSL_NO_KRB5 - -DOPENSSL_NO_MD2 - -DOPENSSL_NO_MDC2 - #-DOPENSSL_NO_MD4 # MD4 is necessary for MariaDB/MySQL client - -DOPENSSL_NO_RC2 - -DOPENSSL_NO_RC4 - -DOPENSSL_NO_RC5 - -DOPENSSL_NO_RFC3779 - -DOPENSSL_NO_SCTP - -DOPENSSL_NO_STORE - -DOPENSSL_NO_SEED - -DOPENSSL_NO_WHIRLPOOL - -DOPENSSL_NO_RIPEMD - ) - - include_directories( - ${OPENSSL_SOURCES_DIR} - ${OPENSSL_SOURCES_DIR}/crypto - ${OPENSSL_SOURCES_DIR}/crypto/asn1 - ${OPENSSL_SOURCES_DIR}/crypto/modes - ${OPENSSL_SOURCES_DIR}/crypto/evp - ${OPENSSL_SOURCES_DIR}/include - ) - - set(OPENSSL_SOURCES_SUBDIRS - ${OPENSSL_SOURCES_DIR}/crypto - ${OPENSSL_SOURCES_DIR}/crypto/aes - ${OPENSSL_SOURCES_DIR}/crypto/asn1 - ${OPENSSL_SOURCES_DIR}/crypto/bio - ${OPENSSL_SOURCES_DIR}/crypto/bn - ${OPENSSL_SOURCES_DIR}/crypto/buffer - ${OPENSSL_SOURCES_DIR}/crypto/cmac - ${OPENSSL_SOURCES_DIR}/crypto/cms - ${OPENSSL_SOURCES_DIR}/crypto/comp - ${OPENSSL_SOURCES_DIR}/crypto/conf - ${OPENSSL_SOURCES_DIR}/crypto/des - ${OPENSSL_SOURCES_DIR}/crypto/dh - ${OPENSSL_SOURCES_DIR}/crypto/dsa - ${OPENSSL_SOURCES_DIR}/crypto/dso - ${OPENSSL_SOURCES_DIR}/crypto/engine - ${OPENSSL_SOURCES_DIR}/crypto/err - ${OPENSSL_SOURCES_DIR}/crypto/evp - ${OPENSSL_SOURCES_DIR}/crypto/hmac - ${OPENSSL_SOURCES_DIR}/crypto/lhash - ${OPENSSL_SOURCES_DIR}/crypto/md4 - ${OPENSSL_SOURCES_DIR}/crypto/md5 - ${OPENSSL_SOURCES_DIR}/crypto/modes - ${OPENSSL_SOURCES_DIR}/crypto/objects - ${OPENSSL_SOURCES_DIR}/crypto/ocsp - ${OPENSSL_SOURCES_DIR}/crypto/pem - ${OPENSSL_SOURCES_DIR}/crypto/pkcs12 - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7 - ${OPENSSL_SOURCES_DIR}/crypto/pqueue - ${OPENSSL_SOURCES_DIR}/crypto/rand - ${OPENSSL_SOURCES_DIR}/crypto/rsa - ${OPENSSL_SOURCES_DIR}/crypto/sha - ${OPENSSL_SOURCES_DIR}/crypto/srp - ${OPENSSL_SOURCES_DIR}/crypto/stack - ${OPENSSL_SOURCES_DIR}/crypto/ts - ${OPENSSL_SOURCES_DIR}/crypto/txt_db - ${OPENSSL_SOURCES_DIR}/crypto/ui - ${OPENSSL_SOURCES_DIR}/crypto/x509 - ${OPENSSL_SOURCES_DIR}/crypto/x509v3 - ${OPENSSL_SOURCES_DIR}/ssl - ) - - if (ENABLE_OPENSSL_ENGINES) - list(APPEND OPENSSL_SOURCES_SUBDIRS - ${OPENSSL_SOURCES_DIR}/engines - ) - endif() - - list(APPEND OPENSSL_SOURCES_SUBDIRS - # EC, ECDH and ECDSA are necessary for PKCS11, and for contacting - # HTTPS servers that use TLS certificate encrypted with ECDSA - # (check the output of a recent version of the "sslscan" - # command). Until Orthanc <= 1.4.1, these features were only - # enabled if ENABLE_PKCS11 support was set to "ON". - # https://groups.google.com/d/msg/orthanc-users/2l-bhYIMEWg/oMmK33bYBgAJ - ${OPENSSL_SOURCES_DIR}/crypto/ec - ${OPENSSL_SOURCES_DIR}/crypto/ecdh - ${OPENSSL_SOURCES_DIR}/crypto/ecdsa - ) - - foreach(d ${OPENSSL_SOURCES_SUBDIRS}) - AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES) - endforeach() - - list(REMOVE_ITEM OPENSSL_SOURCES - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c - ${OPENSSL_SOURCES_DIR}/crypto/armcap.c - ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp - ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c - ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c - ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c - ${OPENSSL_SOURCES_DIR}/crypto/des/des.c - ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp - ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c - ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp - ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c - ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c - ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c - ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.c - ${OPENSSL_SOURCES_DIR}/crypto/md4/md4s.cpp - ${OPENSSL_SOURCES_DIR}/crypto/md4/md4test.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c - ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c - ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c - ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c - ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c - ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c - ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c - ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c - ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c - ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c - ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c - ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c - ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c - - ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c - ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c - ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c - ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c - ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c - ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c - ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c - ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c - - ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_extra_test.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/verify_extra_test.c - ${OPENSSL_SOURCES_DIR}/crypto/x509/verify_extra_test.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3prin.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3nametest.c - ${OPENSSL_SOURCES_DIR}/crypto/constant_time_test.c - - ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.c - ${OPENSSL_SOURCES_DIR}/ssl/fatalerrtest.c - ${OPENSSL_SOURCES_DIR}/ssl/dtlstest.c - ${OPENSSL_SOURCES_DIR}/ssl/bad_dtls_test.c - ${OPENSSL_SOURCES_DIR}/ssl/clienthellotest.c - ${OPENSSL_SOURCES_DIR}/ssl/sslv2conftest.c - - ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256.c - ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c - ${OPENSSL_SOURCES_DIR}/crypto/ec/ectest.c - ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdhtest.c - ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsatest.c - ) - - - if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") - set_source_files_properties( - ${OPENSSL_SOURCES} - PROPERTIES COMPILE_DEFINITIONS - "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN") - - if (ENABLE_OPENSSL_ENGINES) - link_libraries(crypt32) - endif() + message(FATAL_ERROR "Unsupported version of OpenSSL: ${OPENSSL_STATIC_VERSION}") endif() source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/OpenSslConfigurationStatic-1.0.2.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,332 @@ +SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2p) +SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.0.2p.tar.gz") +SET(OPENSSL_MD5 "ac5eb30bf5798aa14b1ae6d0e7da58df") + +if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}") + set(FirstRun OFF) +else() + set(FirstRun ON) +endif() + +DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}") + +if (FirstRun) + file(MAKE_DIRECTORY ${OPENSSL_SOURCES_DIR}/include/openssl) + + foreach(header + ${OPENSSL_SOURCES_DIR}/crypto/aes/aes.h + ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1.h + ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1_mac.h + ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1t.h + ${OPENSSL_SOURCES_DIR}/crypto/bf/blowfish.h + ${OPENSSL_SOURCES_DIR}/crypto/bio/bio.h + ${OPENSSL_SOURCES_DIR}/crypto/bn/bn.h + ${OPENSSL_SOURCES_DIR}/crypto/buffer/buffer.h + ${OPENSSL_SOURCES_DIR}/crypto/camellia/camellia.h + ${OPENSSL_SOURCES_DIR}/crypto/cast/cast.h + ${OPENSSL_SOURCES_DIR}/crypto/cmac/cmac.h + ${OPENSSL_SOURCES_DIR}/crypto/cms/cms.h + ${OPENSSL_SOURCES_DIR}/crypto/comp/comp.h + ${OPENSSL_SOURCES_DIR}/crypto/conf/conf.h + ${OPENSSL_SOURCES_DIR}/crypto/conf/conf_api.h + ${OPENSSL_SOURCES_DIR}/crypto/crypto.h + ${OPENSSL_SOURCES_DIR}/crypto/des/des.h + ${OPENSSL_SOURCES_DIR}/crypto/des/des_old.h + ${OPENSSL_SOURCES_DIR}/crypto/dh/dh.h + ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsa.h + ${OPENSSL_SOURCES_DIR}/crypto/dso/dso.h + ${OPENSSL_SOURCES_DIR}/crypto/ebcdic.h + ${OPENSSL_SOURCES_DIR}/crypto/ec/ec.h + ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdh.h + ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsa.h + ${OPENSSL_SOURCES_DIR}/crypto/engine/engine.h + ${OPENSSL_SOURCES_DIR}/crypto/err/err.h + ${OPENSSL_SOURCES_DIR}/crypto/evp/evp.h + ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmac.h + ${OPENSSL_SOURCES_DIR}/crypto/idea/idea.h + ${OPENSSL_SOURCES_DIR}/crypto/jpake/jpake.h + ${OPENSSL_SOURCES_DIR}/crypto/krb5/krb5_asn.h + ${OPENSSL_SOURCES_DIR}/crypto/lhash/lhash.h + ${OPENSSL_SOURCES_DIR}/crypto/md2/md2.h + ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.h + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.h + ${OPENSSL_SOURCES_DIR}/crypto/mdc2/mdc2.h + ${OPENSSL_SOURCES_DIR}/crypto/modes/modes.h + ${OPENSSL_SOURCES_DIR}/crypto/objects/obj_mac.h + ${OPENSSL_SOURCES_DIR}/crypto/objects/objects.h + ${OPENSSL_SOURCES_DIR}/crypto/ocsp/ocsp.h + ${OPENSSL_SOURCES_DIR}/crypto/opensslconf.h + ${OPENSSL_SOURCES_DIR}/crypto/opensslv.h + ${OPENSSL_SOURCES_DIR}/crypto/ossl_typ.h + ${OPENSSL_SOURCES_DIR}/crypto/pem/pem.h + ${OPENSSL_SOURCES_DIR}/crypto/pem/pem2.h + ${OPENSSL_SOURCES_DIR}/crypto/pkcs12/pkcs12.h + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pkcs7.h + ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pqueue.h + ${OPENSSL_SOURCES_DIR}/crypto/rand/rand.h + ${OPENSSL_SOURCES_DIR}/crypto/rc2/rc2.h + ${OPENSSL_SOURCES_DIR}/crypto/rc4/rc4.h + ${OPENSSL_SOURCES_DIR}/crypto/rc5/rc5.h + ${OPENSSL_SOURCES_DIR}/crypto/ripemd/ripemd.h + ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa.h + ${OPENSSL_SOURCES_DIR}/crypto/seed/seed.h + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.h + ${OPENSSL_SOURCES_DIR}/crypto/srp/srp.h + ${OPENSSL_SOURCES_DIR}/crypto/stack/safestack.h + ${OPENSSL_SOURCES_DIR}/crypto/stack/stack.h + ${OPENSSL_SOURCES_DIR}/crypto/store/store.h + ${OPENSSL_SOURCES_DIR}/crypto/symhacks.h + ${OPENSSL_SOURCES_DIR}/crypto/ts/ts.h + ${OPENSSL_SOURCES_DIR}/crypto/txt_db/txt_db.h + ${OPENSSL_SOURCES_DIR}/crypto/ui/ui.h + ${OPENSSL_SOURCES_DIR}/crypto/ui/ui_compat.h + ${OPENSSL_SOURCES_DIR}/crypto/whrlpool/whrlpool.h + ${OPENSSL_SOURCES_DIR}/crypto/x509/x509.h + ${OPENSSL_SOURCES_DIR}/crypto/x509/x509_vfy.h + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/x509v3.h + ${OPENSSL_SOURCES_DIR}/e_os2.h + ${OPENSSL_SOURCES_DIR}/ssl/dtls1.h + ${OPENSSL_SOURCES_DIR}/ssl/kssl.h + ${OPENSSL_SOURCES_DIR}/ssl/srtp.h + ${OPENSSL_SOURCES_DIR}/ssl/ssl.h + ${OPENSSL_SOURCES_DIR}/ssl/ssl2.h + ${OPENSSL_SOURCES_DIR}/ssl/ssl23.h + ${OPENSSL_SOURCES_DIR}/ssl/ssl3.h + ${OPENSSL_SOURCES_DIR}/ssl/tls1.h + ) + file(COPY ${header} DESTINATION ${OPENSSL_SOURCES_DIR}/include/openssl) + endforeach() + + file(RENAME + ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h + ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2_source.h) + + # The following patch of "e_os2.h" prevents from building OpenSSL + # as a DLL under Windows. Otherwise, symbols have inconsistent + # linkage if ${OPENSSL_SOURCES} is used to create a DLL (notably + # if building an Orthanc plugin such as MySQL). + file(WRITE ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h " +#include \"e_os2_source.h\" +#if defined(_WIN32) +# undef OPENSSL_EXPORT +# undef OPENSSL_IMPORT +# undef OPENSSL_EXTERN +# undef OPENSSL_GLOBAL +# define OPENSSL_EXPORT +# define OPENSSL_IMPORT +# define OPENSSL_EXTERN extern +# define OPENSSL_GLOBAL +#endif +") +endif() + +add_definitions( + -DOPENSSL_THREADS + -DOPENSSL_IA32_SSE2 + -DOPENSSL_NO_ASM + -DOPENSSL_NO_DYNAMIC_ENGINE + -DNO_WINDOWS_BRAINDEATH + + -DOPENSSL_NO_BF + -DOPENSSL_NO_CAMELLIA + -DOPENSSL_NO_CAST + -DOPENSSL_NO_EC_NISTP_64_GCC_128 + -DOPENSSL_NO_GMP + -DOPENSSL_NO_GOST + -DOPENSSL_NO_HW + -DOPENSSL_NO_JPAKE + -DOPENSSL_NO_IDEA + -DOPENSSL_NO_KRB5 + -DOPENSSL_NO_MD2 + -DOPENSSL_NO_MDC2 + #-DOPENSSL_NO_MD4 # MD4 is necessary for MariaDB/MySQL client + -DOPENSSL_NO_RC2 + -DOPENSSL_NO_RC4 + -DOPENSSL_NO_RC5 + -DOPENSSL_NO_RFC3779 + -DOPENSSL_NO_SCTP + -DOPENSSL_NO_STORE + -DOPENSSL_NO_SEED + -DOPENSSL_NO_WHIRLPOOL + -DOPENSSL_NO_RIPEMD + ) + +include_directories( + ${OPENSSL_SOURCES_DIR} + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/include + ) + +set(OPENSSL_SOURCES_SUBDIRS + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/aes + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/bio + ${OPENSSL_SOURCES_DIR}/crypto/bn + ${OPENSSL_SOURCES_DIR}/crypto/buffer + ${OPENSSL_SOURCES_DIR}/crypto/cmac + ${OPENSSL_SOURCES_DIR}/crypto/cms + ${OPENSSL_SOURCES_DIR}/crypto/comp + ${OPENSSL_SOURCES_DIR}/crypto/conf + ${OPENSSL_SOURCES_DIR}/crypto/des + ${OPENSSL_SOURCES_DIR}/crypto/dh + ${OPENSSL_SOURCES_DIR}/crypto/dsa + ${OPENSSL_SOURCES_DIR}/crypto/dso + ${OPENSSL_SOURCES_DIR}/crypto/engine + ${OPENSSL_SOURCES_DIR}/crypto/err + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/crypto/hmac + ${OPENSSL_SOURCES_DIR}/crypto/lhash + ${OPENSSL_SOURCES_DIR}/crypto/md4 + ${OPENSSL_SOURCES_DIR}/crypto/md5 + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/crypto/objects + ${OPENSSL_SOURCES_DIR}/crypto/ocsp + ${OPENSSL_SOURCES_DIR}/crypto/pem + ${OPENSSL_SOURCES_DIR}/crypto/pkcs12 + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7 + ${OPENSSL_SOURCES_DIR}/crypto/pqueue + ${OPENSSL_SOURCES_DIR}/crypto/rand + ${OPENSSL_SOURCES_DIR}/crypto/rsa + ${OPENSSL_SOURCES_DIR}/crypto/sha + ${OPENSSL_SOURCES_DIR}/crypto/srp + ${OPENSSL_SOURCES_DIR}/crypto/stack + ${OPENSSL_SOURCES_DIR}/crypto/ts + ${OPENSSL_SOURCES_DIR}/crypto/txt_db + ${OPENSSL_SOURCES_DIR}/crypto/ui + ${OPENSSL_SOURCES_DIR}/crypto/x509 + ${OPENSSL_SOURCES_DIR}/crypto/x509v3 + ${OPENSSL_SOURCES_DIR}/ssl + ) + +if (ENABLE_OPENSSL_ENGINES) + list(APPEND OPENSSL_SOURCES_SUBDIRS + ${OPENSSL_SOURCES_DIR}/engines + ) +endif() + +list(APPEND OPENSSL_SOURCES_SUBDIRS + # EC, ECDH and ECDSA are necessary for PKCS11, and for contacting + # HTTPS servers that use TLS certificate encrypted with ECDSA + # (check the output of a recent version of the "sslscan" + # command). Until Orthanc <= 1.4.1, these features were only + # enabled if ENABLE_PKCS11 support was set to "ON". + # https://groups.google.com/d/msg/orthanc-users/2l-bhYIMEWg/oMmK33bYBgAJ + ${OPENSSL_SOURCES_DIR}/crypto/ec + ${OPENSSL_SOURCES_DIR}/crypto/ecdh + ${OPENSSL_SOURCES_DIR}/crypto/ecdsa + ) + +foreach(d ${OPENSSL_SOURCES_SUBDIRS}) + AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES) +endforeach() + +list(REMOVE_ITEM OPENSSL_SOURCES + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c + ${OPENSSL_SOURCES_DIR}/crypto/armcap.c + ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp + ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c + ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c + ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c + ${OPENSSL_SOURCES_DIR}/crypto/des/des.c + ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c + ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp + ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c + ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c + ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c + ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.c + ${OPENSSL_SOURCES_DIR}/crypto/md4/md4s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/md4/md4test.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c + ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c + ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c + ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c + ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c + ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c + ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c + ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c + ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c + ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c + ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c + ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c + ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c + + ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c + ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c + ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c + ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c + ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c + ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c + ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c + ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c + + ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_extra_test.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/verify_extra_test.c + ${OPENSSL_SOURCES_DIR}/crypto/x509/verify_extra_test.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3prin.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3nametest.c + ${OPENSSL_SOURCES_DIR}/crypto/constant_time_test.c + + ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.c + ${OPENSSL_SOURCES_DIR}/ssl/fatalerrtest.c + ${OPENSSL_SOURCES_DIR}/ssl/dtlstest.c + ${OPENSSL_SOURCES_DIR}/ssl/bad_dtls_test.c + ${OPENSSL_SOURCES_DIR}/ssl/clienthellotest.c + ${OPENSSL_SOURCES_DIR}/ssl/sslv2conftest.c + + ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256.c + ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c + ${OPENSSL_SOURCES_DIR}/crypto/ec/ectest.c + ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdhtest.c + ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsatest.c + ) + + +if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") + set_source_files_properties( + ${OPENSSL_SOURCES} + PROPERTIES COMPILE_DEFINITIONS + "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN") + + if (ENABLE_OPENSSL_ENGINES) + link_libraries(crypt32) + endif() +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,245 @@ +SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.1.1d) +SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.1.1d.tar.gz") +SET(OPENSSL_MD5 "3be209000dbc7e1b95bcdf47980a3baa") + +if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}") + set(FirstRun OFF) +else() + set(FirstRun ON) +endif() + +DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}") + +if (FirstRun) + file(WRITE ${OPENSSL_SOURCES_DIR}/crypto/buildinf.h " +#define DATE \"\" +#define PLATFORM \"\" +#define compiler_flags \"\" +") + file(WRITE ${OPENSSL_SOURCES_DIR}/crypto/include/internal/bn_conf.h "") + file(WRITE ${OPENSSL_SOURCES_DIR}/crypto/include/internal/dso_conf.h "") + + configure_file( + ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1d-conf.h.in + ${OPENSSL_SOURCES_DIR}/include/openssl/opensslconf.h + ) + + # Apply the patches + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1d.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() +else() + message("The patches for OpenSSL have already been applied") +endif() + +add_definitions( + -DOPENSSL_THREADS + -DOPENSSL_IA32_SSE2 + -DOPENSSL_NO_ASM + -DOPENSSL_NO_DYNAMIC_ENGINE + -DOPENSSL_NO_DEVCRYPTOENG + + -DOPENSSL_NO_BF + -DOPENSSL_NO_CAMELLIA + -DOPENSSL_NO_CAST + -DOPENSSL_NO_EC_NISTP_64_GCC_128 + -DOPENSSL_NO_GMP + -DOPENSSL_NO_GOST + -DOPENSSL_NO_HW + -DOPENSSL_NO_JPAKE + -DOPENSSL_NO_IDEA + -DOPENSSL_NO_KRB5 + -DOPENSSL_NO_MD2 + -DOPENSSL_NO_MDC2 + #-DOPENSSL_NO_MD4 # MD4 is necessary for MariaDB/MySQL client + -DOPENSSL_NO_RC2 + -DOPENSSL_NO_RC4 + -DOPENSSL_NO_RC5 + -DOPENSSL_NO_RFC3779 + -DOPENSSL_NO_SCTP + -DOPENSSL_NO_STORE + -DOPENSSL_NO_SEED + -DOPENSSL_NO_WHIRLPOOL + -DOPENSSL_NO_RIPEMD + -DOPENSSL_NO_AFALGENG + + -DOPENSSLDIR="/usr/local/ssl" + ) + + +include_directories( + ${OPENSSL_SOURCES_DIR} + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448 + ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448/arch_32 + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/crypto/include + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/include + ) + + +set(OPENSSL_SOURCES_SUBDIRS + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/aes + ${OPENSSL_SOURCES_DIR}/crypto/aria + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/async + ${OPENSSL_SOURCES_DIR}/crypto/async/arch + ${OPENSSL_SOURCES_DIR}/crypto/bio + ${OPENSSL_SOURCES_DIR}/crypto/blake2 + ${OPENSSL_SOURCES_DIR}/crypto/bn + ${OPENSSL_SOURCES_DIR}/crypto/buffer + ${OPENSSL_SOURCES_DIR}/crypto/chacha + ${OPENSSL_SOURCES_DIR}/crypto/cmac + ${OPENSSL_SOURCES_DIR}/crypto/cms + ${OPENSSL_SOURCES_DIR}/crypto/comp + ${OPENSSL_SOURCES_DIR}/crypto/conf + ${OPENSSL_SOURCES_DIR}/crypto/ct + ${OPENSSL_SOURCES_DIR}/crypto/des + ${OPENSSL_SOURCES_DIR}/crypto/dh + ${OPENSSL_SOURCES_DIR}/crypto/dsa + ${OPENSSL_SOURCES_DIR}/crypto/dso + ${OPENSSL_SOURCES_DIR}/crypto/ec + ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448 + ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448/arch_32 + ${OPENSSL_SOURCES_DIR}/crypto/err + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/crypto/hmac + ${OPENSSL_SOURCES_DIR}/crypto/kdf + ${OPENSSL_SOURCES_DIR}/crypto/lhash + ${OPENSSL_SOURCES_DIR}/crypto/md4 + ${OPENSSL_SOURCES_DIR}/crypto/md5 + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/crypto/objects + ${OPENSSL_SOURCES_DIR}/crypto/ocsp + ${OPENSSL_SOURCES_DIR}/crypto/pem + ${OPENSSL_SOURCES_DIR}/crypto/pkcs12 + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7 + ${OPENSSL_SOURCES_DIR}/crypto/poly1305 + ${OPENSSL_SOURCES_DIR}/crypto/pqueue + ${OPENSSL_SOURCES_DIR}/crypto/rand + ${OPENSSL_SOURCES_DIR}/crypto/ripemd + ${OPENSSL_SOURCES_DIR}/crypto/rsa + ${OPENSSL_SOURCES_DIR}/crypto/sha + ${OPENSSL_SOURCES_DIR}/crypto/siphash + ${OPENSSL_SOURCES_DIR}/crypto/sm2 + ${OPENSSL_SOURCES_DIR}/crypto/sm3 + ${OPENSSL_SOURCES_DIR}/crypto/sm4 + ${OPENSSL_SOURCES_DIR}/crypto/srp + ${OPENSSL_SOURCES_DIR}/crypto/stack + ${OPENSSL_SOURCES_DIR}/crypto/store + ${OPENSSL_SOURCES_DIR}/crypto/ts + ${OPENSSL_SOURCES_DIR}/crypto/txt_db + ${OPENSSL_SOURCES_DIR}/crypto/ui + ${OPENSSL_SOURCES_DIR}/crypto/x509 + ${OPENSSL_SOURCES_DIR}/crypto/x509v3 + ${OPENSSL_SOURCES_DIR}/ssl + ${OPENSSL_SOURCES_DIR}/ssl/record + ${OPENSSL_SOURCES_DIR}/ssl/statem + ) + +if (ENABLE_OPENSSL_ENGINES) + add_definitions( + #-DENGINESDIR="/usr/local/lib/engines-1.1" # On GNU/Linux + -DENGINESDIR="." + ) + + list(APPEND OPENSSL_SOURCES_SUBDIRS + ${OPENSSL_SOURCES_DIR}/engines + ${OPENSSL_SOURCES_DIR}/crypto/engine + ) +else() + add_definitions(-DOPENSSL_NO_ENGINE) +endif() + +list(APPEND OPENSSL_SOURCES_SUBDIRS + # EC, ECDH and ECDSA are necessary for PKCS11, and for contacting + # HTTPS servers that use TLS certificate encrypted with ECDSA + # (check the output of a recent version of the "sslscan" + # command). Until Orthanc <= 1.4.1, these features were only + # enabled if ENABLE_PKCS11 support was set to "ON". + # https://groups.google.com/d/msg/orthanc-users/2l-bhYIMEWg/oMmK33bYBgAJ + ${OPENSSL_SOURCES_DIR}/crypto/ec + ${OPENSSL_SOURCES_DIR}/crypto/ecdh + ${OPENSSL_SOURCES_DIR}/crypto/ecdsa + ) + +foreach(d ${OPENSSL_SOURCES_SUBDIRS}) + AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES) +endforeach() + +list(REMOVE_ITEM OPENSSL_SOURCES + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c + ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c + ${OPENSSL_SOURCES_DIR}/crypto/armcap.c + ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c + ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c + ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256.c + ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c + ${OPENSSL_SOURCES_DIR}/crypto/engine/eng_devcrypto.c + ${OPENSSL_SOURCES_DIR}/crypto/poly1305/poly1305_base2_44.c # Cannot be compiled with MinGW + ${OPENSSL_SOURCES_DIR}/crypto/poly1305/poly1305_ieee754.c # Cannot be compiled with MinGW + ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c + ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c + ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c + ${OPENSSL_SOURCES_DIR}/engines/e_afalg.c # Cannot be compiled with MinGW + ) + +# Check out "${OPENSSL_SOURCES_DIR}/Configurations/README": "This is +# default if no option is specified, it works on any supported +# system." It is mandatory to define it as a macro, as it is used by +# all the source files that include OpenSSL (e.g. "Core/Toolbox.cpp" +# or curl) +add_definitions(-DTHIRTY_TWO_BIT) + + +if (NOT CMAKE_COMPILER_IS_GNUCXX OR + "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" OR + "${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + # Disable the use of a gcc extension, that is neither available on + # MinGW, nor on LSB + add_definitions( + -DOPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE + ) +endif() + + +if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") + set(OPENSSL_DEFINITIONS + "${OPENSSL_DEFINITIONS};OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN;NO_WINDOWS_BRAINDEATH") + + if (ENABLE_OPENSSL_ENGINES) + link_libraries(crypt32) + endif() + + add_definitions( + -DOPENSSL_RAND_SEED_OS # ${OPENSSL_SOURCES_DIR}/crypto/rand/rand_win.c + ) + +elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + # In order for "crypto/mem_sec.c" to compile on LSB + add_definitions( + -DOPENSSL_NO_SECURE_MEMORY + ) +endif() + + +set_source_files_properties( + ${OPENSSL_SOURCES} + PROPERTIES COMPILE_DEFINITIONS + "${OPENSSL_DEFINITIONS};DSO_NONE" + )
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -107,6 +107,7 @@ add_definitions( -DORTHANC_ENABLE_DCMTK=0 -DORTHANC_ENABLE_DCMTK_NETWORKING=0 + -DORTHANC_ENABLE_DCMTK_TRANSCODING=0 ) unset(DCMTK_DICTIONARY_DIR CACHE) unset(DCMTK_VERSION CACHE) @@ -123,6 +124,8 @@ set(ORTHANC_CORE_SOURCES_INTERNAL ${ORTHANC_ROOT}/Core/Cache/MemoryCache.cpp + ${ORTHANC_ROOT}/Core/Cache/MemoryObjectCache.cpp + ${ORTHANC_ROOT}/Core/Cache/MemoryStringCache.cpp ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp ${ORTHANC_ROOT}/Core/EnumerationDictionary.h @@ -461,6 +464,7 @@ ${ORTHANC_ROOT}/Core/DicomParsing/DicomModification.cpp ${ORTHANC_ROOT}/Core/DicomParsing/DicomWebJsonVisitor.cpp ${ORTHANC_ROOT}/Core/DicomParsing/FromDcmtkBridge.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomDir.cpp ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomFile.cpp ${ORTHANC_ROOT}/Core/DicomParsing/ToDcmtkBridge.cpp @@ -491,6 +495,13 @@ add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=0) endif() + # New in Orthanc 1.6.0 + if (ENABLE_DCMTK_TRANSCODING) + add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=1) + else() + add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=0) + endif() + if (STANDALONE_BUILD AND NOT HAS_EMBEDDED_RESOURCES) EmbedResources( ${DCMTK_DICTIONARIES}
--- a/Resources/CMake/OrthancFrameworkParameters.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/CMake/OrthancFrameworkParameters.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ ##################################################################### # Version of the build, should always be "mainline" except in release branches -set(ORTHANC_VERSION "1.5.8") +set(ORTHANC_VERSION "1.6.0") # Version of the database schema. History: # * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning @@ -17,7 +17,7 @@ # Version of the Orthanc API, can be retrieved from "/system" URI in # order to check whether new URI endpoints are available even if using # the mainline version of Orthanc -set(ORTHANC_API_VERSION "4") +set(ORTHANC_API_VERSION "5") ##################################################################### @@ -57,7 +57,7 @@ # Parameters specific to DCMTK set(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") -set(DCMTK_STATIC_VERSION "3.6.4" CACHE STRING "Version of DCMTK to be used in static builds (can be \"3.6.0\", \"3.6.2\", or \"3.6.4\")") +set(DCMTK_STATIC_VERSION "3.6.5" CACHE STRING "Version of DCMTK to be used in static builds (can be \"3.6.0\", \"3.6.2\", \"3.6.4\", or \"3.6.5\")") set(USE_DCMTK_362_PRIVATE_DIC ON CACHE BOOL "Use the dictionary of private tags from DCMTK 3.6.2 if using DCMTK 3.6.0") set(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK") set(ENABLE_DCMTK_LOG ON CACHE BOOL "Enable logging internal to DCMTK") @@ -73,6 +73,7 @@ set(USE_LEGACY_LIBICU OFF CACHE BOOL "Use icu icu4c-58_2, latest version not requiring a C++11 compiler (for LSB and old versions of Visual Studio)") set(MSVC_MULTIPLE_PROCESSES OFF CACHE BOOL "Add the /MP option to build with multiple processes if using Visual Studio") set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND OFF CACHE BOOL "Sets the compiler flags required to use the LLVM Web Assembly backend in emscripten") +set(OPENSSL_STATIC_VERSION "1.1.1" CACHE STRING "Version of OpenSSL to be used in static builds (can be \"1.0.2\", or \"1.1.1\")") mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS) @@ -102,6 +103,7 @@ set(ENABLE_WEB_SERVER OFF CACHE INTERNAL "Enable embedded Web server") set(ENABLE_DCMTK OFF CACHE INTERNAL "Enable DCMTK") set(ENABLE_DCMTK_NETWORKING OFF CACHE INTERNAL "Enable DICOM networking in DCMTK") +set(ENABLE_DCMTK_TRANSCODING OFF CACHE INTERNAL "Enable DICOM transcoding in DCMTK") set(ENABLE_OPENSSL_ENGINES OFF CACHE INTERNAL "Enable support of engines in OpenSSL") set(HAS_EMBEDDED_RESOURCES OFF CACHE INTERNAL
--- a/Resources/Configuration.json Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Configuration.json Thu Mar 19 11:48:30 2020 +0100 @@ -96,7 +96,8 @@ // receive files or to do query/retrieve through the DICOM protocol. "DicomServerEnabled" : true, - // The DICOM Application Entity Title + // The DICOM Application Entity Title (cannot be longer than 16 + // characters) "DicomAet" : "ORTHANC", // Check whether the called AET corresponds to the AET of Orthanc @@ -123,6 +124,7 @@ "JpipTransferSyntaxAccepted" : true, "Mpeg2TransferSyntaxAccepted" : true, "RleTransferSyntaxAccepted" : true, + "Mpeg4TransferSyntaxAccepted" : true, // New in Orthanc 1.6.0 // Whether Orthanc accepts to act as C-Store SCP for unknown storage // SOP classes (aka. "promiscuous mode") @@ -175,8 +177,12 @@ /** * 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". + * store (shipped in the DCMTK distribution), as started by the + * command line "storescp 2000". The first parameter is the + * AET of the remote modality (cannot be longer than 16 + * characters), the second one is the remote network address, + * and the third one is the TCP port number corresponding + * to the DICOM protocol on the remote modality (usually 104). **/ // "sample" : [ "STORESCP", "127.0.0.1", 2000 ] @@ -189,34 +195,34 @@ * - "GenericNoUniversalWildcard" (to replace "*" by "" in all fields * in outgoing C-Find SCU requests originating from Orthanc), * - "StoreScp" (storescp tool from DCMTK), - * - "ClearCanvas", - * - "Dcm4Chee", * - "Vitrea", * - "GE" (Enterprise Archive, MRI consoles and Advantage Workstation * from GE Healthcare). * * This parameter is case-sensitive. **/ - // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] + // "vitrea" : [ "VITREA", "192.168.1.1", 104, "Vitrea" ] /** * By default, the Orthanc SCP accepts all DICOM commands (C-ECHO, - * C-STORE, C-FIND, C-MOVE) issued by the registered remote SCU - * modalities. Starting with Orthanc 1.5.0, it is possible to - * specify which DICOM commands are allowed, separately for each - * remote modality, using the syntax below. The "AllowEcho" (resp. - * "AllowStore") option only has an effect respectively if global - * option "DicomAlwaysAllowEcho" (resp. "DicomAlwaysAllowStore") - * is set to false. + * C-STORE, C-FIND, C-MOVE, and storage commitment) issued by the + * registered remote SCU modalities. Starting with Orthanc 1.5.0, + * it is possible to specify which DICOM commands are allowed, + * separately for each remote modality, using the syntax + * below. The "AllowEcho" (resp. "AllowStore") option only has an + * effect respectively if global option "DicomAlwaysAllowEcho" + * (resp. "DicomAlwaysAllowStore") is set to false. **/ //"untrusted" : { // "AET" : "ORTHANC", // "Port" : 104, // "Host" : "127.0.0.1", + // "Manufacturer" : "Generic", // "AllowEcho" : false, // "AllowFind" : false, // "AllowMove" : false, - // "AllowStore" : true + // "AllowStore" : true, + // "AllowStorageCommitment" : false // new in 1.6.0 //} }, @@ -517,5 +523,14 @@ // to option "request_timeout_ms" of Mongoose/Civetweb. It will set // the socket options "SO_RCVTIMEO" and "SO_SNDTIMEO" to the // specified value. - "HttpRequestTimeout" : 30 + "HttpRequestTimeout" : 30, + + // Set the default private creator that is used by Orthanc when it + // looks for a private tag in its dictionary (cf. "Dictionary" + // option), or when it creates/modifies a DICOM file (new in Orthanc 1.6.0). + "DefaultPrivateCreator" : "", + + // Maximum number of storage commitment reports (i.e. received from + // remote modalities) to be kept in memory (new in Orthanc 1.6.0). + "StorageCommitmentReportsSize" : 100 }
--- a/Resources/DicomConformanceStatement.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/DicomConformanceStatement.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/DicomTransferSyntaxes.json Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,376 @@ +[ + { + "UID" : "1.2.840.10008.1.2", + "Name" : "Implicit VR Little Endian", + "Value" : "LittleEndianImplicit", + "Retired" : false, + "DCMTK" : "EXS_LittleEndianImplicit", + "GDCM" : "gdcm::TransferSyntax::ImplicitVRLittleEndian" + }, + + { + "UID" : "1.2.840.10008.1.2.1", + "Name" : "Explicit VR Little Endian", + "Value" : "LittleEndianExplicit", + "Retired" : false, + "DCMTK" : "EXS_LittleEndianExplicit", + "GDCM" : "gdcm::TransferSyntax::ExplicitVRLittleEndian" + }, + + { + "UID" : "1.2.840.10008.1.2.1.99", + "Name" : "Deflated Explicit VR Little Endian", + "Value" : "DeflatedLittleEndianExplicit", + "Retired" : false, + "DCMTK" : "EXS_DeflatedLittleEndianExplicit" + }, + + { + "UID" : "1.2.840.10008.1.2.2", + "Name" : "Explicit VR Big Endian", + "Value" : "BigEndianExplicit", + "Retired" : false, + "DCMTK" : "EXS_BigEndianExplicit" + }, + + { + "UID" : "1.2.840.10008.1.2.4.50", + "Name" : "JPEG Baseline (process 1, lossy)", + "Value" : "JPEGProcess1", + "Retired" : false, + "Note" : "Default Transfer Syntax for Lossy JPEG 8-bit Image Compression", + "DCMTK" : "EXS_JPEGProcess1", + "DCMTK360" : "EXS_JPEGProcess1TransferSyntax", + "GDCM" : "gdcm::TransferSyntax::JPEGBaselineProcess1" + }, + + { + "UID" : "1.2.840.10008.1.2.4.51", + "Name" : "JPEG Extended Sequential (processes 2 & 4)", + "Value" : "JPEGProcess2_4", + "Retired" : false, + "Note" : "Default Transfer Syntax for Lossy JPEG (lossy, 8/12 bit), 12-bit Image Compression (Process 4 only)", + "DCMTK" : "EXS_JPEGProcess2_4", + "DCMTK360" : "EXS_JPEGProcess2_4TransferSyntax", + "GDCM" : "gdcm::TransferSyntax::JPEGExtendedProcess2_4" + }, + + { + "UID" : "1.2.840.10008.1.2.4.52", + "Name" : "JPEG Extended Sequential (lossy, 8/12 bit), arithmetic coding", + "Value" : "JPEGProcess3_5", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess3_5", + "DCMTK360" : "EXS_JPEGProcess3_5TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.53", + "Name" : "JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit)", + "Value" : "JPEGProcess6_8", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess6_8", + "DCMTK360" : "EXS_JPEGProcess6_8TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.54", + "Name" : "JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit), arithmetic coding", + "Value" : "JPEGProcess7_9", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess7_9", + "DCMTK360" : "EXS_JPEGProcess7_9TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.55", + "Name" : "JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit)", + "Value" : "JPEGProcess10_12", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess10_12", + "DCMTK360" : "EXS_JPEGProcess10_12TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.56", + "Name" : "JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit), arithmetic coding", + "Value" : "JPEGProcess11_13", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess11_13", + "DCMTK360" : "EXS_JPEGProcess11_13TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.57", + "Name" : "JPEG Lossless, Nonhierarchical with any selection value (process 14)", + "Value" : "JPEGProcess14", + "Retired" : false, + "DCMTK" : "EXS_JPEGProcess14", + "DCMTK360" : "EXS_JPEGProcess14TransferSyntax", + "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14" + }, + + { + "UID" : "1.2.840.10008.1.2.4.58", + "Name" : "JPEG Lossless with any selection value, arithmetic coding", + "Value" : "JPEGProcess15", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess15", + "DCMTK360" : "EXS_JPEGProcess15TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.59", + "Name" : "JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit)", + "Value" : "JPEGProcess16_18", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess16_18", + "DCMTK360" : "EXS_JPEGProcess16_18TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.60", + "Name" : "JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit), arithmetic coding", + "Value" : "JPEGProcess17_19", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess17_19", + "DCMTK360" : "EXS_JPEGProcess17_19TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.61", + "Name" : "JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit)", + "Value" : "JPEGProcess20_22", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess20_22", + "DCMTK360" : "EXS_JPEGProcess20_22TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.62", + "Name" : "JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit), arithmetic coding", + "Value" : "JPEGProcess21_23", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess21_23", + "DCMTK360" : "EXS_JPEGProcess21_23TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.63", + "Name" : "JPEG Full Progression, Hierarchical (lossy, 8/12 bit)", + "Value" : "JPEGProcess24_26", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess24_26", + "DCMTK360" : "EXS_JPEGProcess24_26TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.64", + "Name" : "JPEG Full Progression, Hierarchical (lossy, 8/12 bit), arithmetic coding", + "Value" : "JPEGProcess25_27", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess25_27", + "DCMTK360" : "EXS_JPEGProcess25_27TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.65", + "Name" : "JPEG Lossless, Hierarchical", + "Value" : "JPEGProcess28", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess28", + "DCMTK360" : "EXS_JPEGProcess28TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.66", + "Name" : "JPEG Lossless, Hierarchical, arithmetic coding", + "Value" : "JPEGProcess29", + "Retired" : true, + "DCMTK" : "EXS_JPEGProcess29", + "DCMTK360" : "EXS_JPEGProcess29TransferSyntax" + }, + + { + "UID" : "1.2.840.10008.1.2.4.70", + "Name" : "JPEG Lossless, Nonhierarchical, First-Order Prediction (Processes 14 [Selection Value 1])", + "Value" : "JPEGProcess14SV1", + "Retired" : false, + "Note" : "Default Transfer Syntax for Lossless JPEG Image Compression", + "DCMTK" : "EXS_JPEGProcess14SV1", + "DCMTK360" : "EXS_JPEGProcess14SV1TransferSyntax", + "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14_1" + }, + + { + "UID" : "1.2.840.10008.1.2.4.80", + "Name" : "JPEG-LS (lossless)", + "Value" : "JPEGLSLossless", + "Retired" : false, + "DCMTK" : "EXS_JPEGLSLossless", + "GDCM" : "gdcm::TransferSyntax::JPEGLSLossless" + }, + + { + "UID" : "1.2.840.10008.1.2.4.81", + "Name" : "JPEG-LS (lossy or near-lossless)", + "Value" : "JPEGLSLossy", + "Retired" : false, + "DCMTK" : "EXS_JPEGLSLossy", + "GDCM" : "gdcm::TransferSyntax::JPEGLSNearLossless" + }, + + { + "UID" : "1.2.840.10008.1.2.4.90", + "Name" : "JPEG 2000 (lossless)", + "Value" : "JPEG2000LosslessOnly", + "Retired" : false, + "DCMTK" : "EXS_JPEG2000LosslessOnly", + "GDCM" : "gdcm::TransferSyntax::JPEG2000Lossless" + }, + + { + "UID" : "1.2.840.10008.1.2.4.91", + "Name" : "JPEG 2000 (lossless or lossy)", + "Value" : "JPEG2000", + "Retired" : false, + "DCMTK" : "EXS_JPEG2000", + "GDCM" : "gdcm::TransferSyntax::JPEG2000" + }, + + { + "UID" : "1.2.840.10008.1.2.4.92", + "Name" : "JPEG 2000 part 2 multicomponent extensions (lossless)", + "Value" : "JPEG2000MulticomponentLosslessOnly", + "Retired" : false, + "DCMTK" : "EXS_JPEG2000MulticomponentLosslessOnly", + "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2Lossless" + }, + + { + "UID" : "1.2.840.10008.1.2.4.93", + "Name" : "JPEG 2000 part 2 multicomponent extensions (lossless or lossy)", + "Value" : "JPEG2000Multicomponent", + "Retired" : false, + "DCMTK" : "EXS_JPEG2000Multicomponent", + "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2" + }, + + { + "UID" : "1.2.840.10008.1.2.4.94", + "Name" : "JPIP Referenced", + "Value" : "JPIPReferenced", + "Retired" : false, + "DCMTK" : "EXS_JPIPReferenced" + }, + + { + "UID" : "1.2.840.10008.1.2.4.95", + "Name" : "JPIP Referenced Deflate", + "Value" : "JPIPReferencedDeflate", + "Retired" : false, + "DCMTK" : "EXS_JPIPReferencedDeflate" + }, + + { + "UID" : "1.2.840.10008.1.2.4.100", + "Name" : "MPEG2 Main Profile at Main Level", + "Value" : "MPEG2MainProfileAtMainLevel", + "Retired" : false, + "DCMTK" : "EXS_MPEG2MainProfileAtMainLevel" + }, + + { + "UID" : "1.2.840.10008.1.2.4.101", + "Name" : "MPEG2 Main Profile at High Level", + "Value" : "MPEG2MainProfileAtHighLevel", + "Retired" : false, + "DCMTK" : "EXS_MPEG2MainProfileAtHighLevel" + }, + + { + "UID" : "1.2.840.10008.1.2.4.102", + "Name" : "MPEG4 High Profile / Level 4.1", + "Value" : "MPEG4HighProfileLevel4_1", + "Retired" : false, + "DCMTK" : "EXS_MPEG4HighProfileLevel4_1", + "SinceDCMTK" : "361" + }, + + { + "UID" : "1.2.840.10008.1.2.4.103", + "Name" : "MPEG4 BD-compatible High Profile / Level 4.1", + "Value" : "MPEG4BDcompatibleHighProfileLevel4_1", + "Retired" : false, + "DCMTK" : "EXS_MPEG4BDcompatibleHighProfileLevel4_1", + "SinceDCMTK" : "361" + }, + + { + "UID" : "1.2.840.10008.1.2.4.104", + "Name" : "MPEG4 High Profile / Level 4.2 For 2D Video", + "Value" : "MPEG4HighProfileLevel4_2_For2DVideo", + "Retired" : false, + "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For2DVideo", + "SinceDCMTK" : "361" + }, + + { + "UID" : "1.2.840.10008.1.2.4.105", + "Name" : "MPEG4 High Profile / Level 4.2 For 3D Video", + "Value" : "MPEG4HighProfileLevel4_2_For3DVideo", + "Retired" : false, + "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For3DVideo", + "SinceDCMTK" : "361" + }, + + { + "UID" : "1.2.840.10008.1.2.4.106", + "Name" : "1.2.840.10008.1.2.4.106", + "Value" : "MPEG4StereoHighProfileLevel4_2", + "Retired" : false, + "DCMTK" : "EXS_MPEG4StereoHighProfileLevel4_2", + "SinceDCMTK" : "361" + }, + + { + "UID" : "1.2.840.10008.1.2.4.107", + "Name" : "HEVC/H.265 Main Profile / Level 5.1", + "Value" : "HEVCMainProfileLevel5_1", + "Retired" : false, + "DCMTK" : "EXS_HEVCMainProfileLevel5_1", + "SinceDCMTK" : "362" + }, + + { + "UID" : "1.2.840.10008.1.2.4.108", + "Name" : "HEVC/H.265 Main 10 Profile / Level 5.1", + "Value" : "HEVCMain10ProfileLevel5_1", + "Retired" : false, + "DCMTK" : "EXS_HEVCMain10ProfileLevel5_1", + "SinceDCMTK" : "362" + }, + + { + "UID" : "1.2.840.10008.1.2.5", + "Name" : "RLE - Run Length Encoding (lossless)", + "Value" : "RLELossless", + "Retired" : false, + "DCMTK" : "EXS_RLELossless", + "GDCM" : "gdcm::TransferSyntax::RLELossless" + }, + + { + "UID" : "1.2.840.10008.1.2.6.1", + "Name" : "RFC 2557 MIME Encapsulation", + "Value" : "RFC2557MimeEncapsulation", + "Retired" : true + }, + + { + "UID" : "1.2.840.10008.1.2.6.2", + "Name" : "XML Encoding", + "Value" : "XML", + "Retired" : true + } +]
--- a/Resources/DownloadOrthancFramework.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/DownloadOrthancFramework.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -1,7 +1,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -110,6 +110,19 @@ set(ORTHANC_FRAMEWORK_MD5 "3c29de1e289b5472342947168f0105c0") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.7") set(ORTHANC_FRAMEWORK_MD5 "e1b76f01116d9b5d4ac8cc39980560e3") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.8") + set(ORTHANC_FRAMEWORK_MD5 "82323e8c49a667f658a3639ea4dbc336") + + # Below this point are development snapshots that were used to + # release some plugin, before an official release of the Orthanc + # framework was available. Here is the command to be used to + # generate a proper archive: + # + # $ hg archive /tmp/Orthanc-`hg id -i | sed 's/\+//'`.tar.gz + # + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "ae0e3fd609df") + # DICOMweb 1.1 (framework pre-1.6.0) + set(ORTHANC_FRAMEWORK_MD5 "7e09e9b530a2f527854f0b782d7e0645") endif() endif() endif()
--- a/Resources/EmbedResources.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/EmbedResources.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -101,6 +101,8 @@ # The resource is a directory: Recursively explore its files content = {} for root, dirs, files in os.walk(pathName): + dirs.sort() + files.sort() base = os.path.relpath(root, pathName) # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
--- a/Resources/ErrorCodes.json Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/ErrorCodes.json Thu Mar 19 11:48:30 2020 +0100 @@ -547,6 +547,11 @@ "Name": "AlreadyExistingTag", "Description": "Cannot override the value of a tag that already exists" }, + { + "Code": 2043, + "Name": "NoStorageCommitmentHandler", + "Description": "No request handler factory for DICOM N-ACTION SCP (storage commitment)" + },
--- a/Resources/Fonts/GenerateFont.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Fonts/GenerateFont.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/GenerateAnonymizationProfile.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/GenerateAnonymizationProfile.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/GenerateErrorCodes.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/GenerateErrorCodes.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/GenerateTransferSyntaxes.py Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,84 @@ +#!/usr/bin/python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., 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/>. + + +import json +import os +import re +import sys +import pystache + +BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + + + +## https://www.dicomlibrary.com/dicom/transfer-syntax/ +## https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EDICOM_transfer_syntax + + +with open(os.path.join(BASE, 'Resources', 'DicomTransferSyntaxes.json'), 'r') as f: + SYNTAXES = json.loads(f.read()) + + + +## +## Generate the "DicomTransferSyntax" enumeration in "Enumerations.h" +## + +path = os.path.join(BASE, 'Core', 'Enumerations.h') +with open(path, 'r') as f: + a = f.read() + +s = ',\n'.join(map(lambda x: ' DicomTransferSyntax_%s /*!< %s */' % (x['Value'], x['Name']), SYNTAXES)) + +a = re.sub('(enum DicomTransferSyntax\s*{)[^}]*?(\s*};)', r'\1\n%s\2' % s, a, re.DOTALL) + +with open(path, 'w') as f: + f.write(a) + + + +## +## Generate the implementations +## + +with open(os.path.join(BASE, 'Core', 'Enumerations_TransferSyntaxes.impl.h'), 'w') as b: + with open(os.path.join(BASE, 'Resources', 'GenerateTransferSyntaxesEnumerations.mustache'), 'r') as a: + b.write(pystache.render(a.read(), { + 'Syntaxes' : SYNTAXES + })) + +with open(os.path.join(BASE, 'Core', 'DicomParsing', 'FromDcmtkBridge_TransferSyntaxes.impl.h'), 'w') as b: + with open(os.path.join(BASE, 'Resources', 'GenerateTransferSyntaxesDcmtk.mustache'), 'r') as a: + b.write(pystache.render(a.read(), { + 'Syntaxes' : SYNTAXES + }))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/GenerateTransferSyntaxesDcmtk.mustache Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,103 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/>. + **/ + +// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py" + +namespace Orthanc +{ + bool FromDcmtkBridge::LookupDcmtkTransferSyntax(E_TransferSyntax& target, + DicomTransferSyntax source) + { + switch (source) + { + {{#Syntaxes}} + {{#DCMTK}} + {{#SinceDCMTK}} +#if DCMTK_VERSION_NUMBER >= {{SinceDCMTK}} + {{/SinceDCMTK}} + case DicomTransferSyntax_{{Value}}: + {{#DCMTK360}} +# if DCMTK_VERSION_NUMBER <= 360 + target = {{DCMTK360}}; +# else + target = {{DCMTK}}; +# endif + {{/DCMTK360}} + {{^DCMTK360}} + target = {{DCMTK}}; + {{/DCMTK360}} + return true; + {{#SinceDCMTK}} +#endif + {{/SinceDCMTK}} + + {{/DCMTK}} + {{/Syntaxes}} + default: + return false; + } + } + + + bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target, + E_TransferSyntax source) + { + switch (source) + { + {{#Syntaxes}} + {{#DCMTK}} + {{#SinceDCMTK}} +#if DCMTK_VERSION_NUMBER >= {{SinceDCMTK}} + {{/SinceDCMTK}} + {{#DCMTK360}} +# if DCMTK_VERSION_NUMBER <= 360 + case {{DCMTK360}}: +# else + case {{DCMTK}}: +# endif + {{/DCMTK360}} + {{^DCMTK360}} + case {{DCMTK}}: + {{/DCMTK360}} + target = DicomTransferSyntax_{{Value}}; + return true; + {{#SinceDCMTK}} +#endif + {{/SinceDCMTK}} + + {{/DCMTK}} + {{/Syntaxes}} + default: + return false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/GenerateTransferSyntaxesEnumerations.mustache Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/>. + **/ + +// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py" + +namespace Orthanc +{ + const char* GetTransferSyntaxUid(DicomTransferSyntax syntax) + { + switch (syntax) + { + {{#Syntaxes}} + case DicomTransferSyntax_{{Value}}: + return "{{UID}}"; + + {{/Syntaxes}} + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool IsRetiredTransferSyntax(DicomTransferSyntax syntax) + { + switch (syntax) + { + {{#Syntaxes}} + case DicomTransferSyntax_{{Value}}: + {{#Retired}} + return true; + {{/Retired}} + {{^Retired}} + return false; + {{/Retired}} + + {{/Syntaxes}} + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool LookupTransferSyntax(DicomTransferSyntax& target, + const std::string& uid) + { + {{#Syntaxes}} + if (uid == "{{UID}}") + { + target = DicomTransferSyntax_{{Value}}; + return true; + } + + {{/Syntaxes}} + return false; + } +}
--- a/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabaseOptimizations/LookupResource.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabaseOptimizations/LookupResource.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/Database.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabasePluginSample/Database.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/Database.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabasePluginSample/Database.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/Plugin.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/DatabasePluginSample/Plugin.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/FromDcmtkBridge.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,168 @@ + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + + if (tag.IsPrivate()) + { + // This raises BitBucket issue 140 (Modifying private tags with + // REST API changes VR from LO to UN) + // https://bitbucket.org/sjodogne/orthanc/issues/140 + LOG(WARNING) << "You are using DCMTK < 3.6.1: All the private tags " + "are considered as having a binary value representation"; + return new DcmOtherByteOtherWord(key); + } + else if (IsBinaryTag(key)) + { + return new DcmOtherByteOtherWord(key); + } + + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * Binary types, handled above + **/ + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: +#endif + +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: +#endif + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_UN: // unknown value representation + case EVR_ox: // OB or OW depending on context + throw OrthancException(ErrorCode_InternalError); + + + /** + * String types. + * http://support.dcmtk.org/docs/classDcmByteString.html + **/ + + case EVR_AS: // age string + return new DcmAgeString(key); + + case EVR_AE: // application entity title + return new DcmApplicationEntity(key); + + case EVR_CS: // code string + return new DcmCodeString(key); + + case EVR_DA: // date string + return new DcmDate(key); + + case EVR_DT: // date time string + return new DcmDateTime(key); + + case EVR_DS: // decimal string + return new DcmDecimalString(key); + + case EVR_IS: // integer string + return new DcmIntegerString(key); + + case EVR_TM: // time string + return new DcmTime(key); + + case EVR_UI: // unique identifier + return new DcmUniqueIdentifier(key); + + case EVR_ST: // short text + return new DcmShortText(key); + + case EVR_LO: // long string + return new DcmLongString(key); + + case EVR_LT: // long text + return new DcmLongText(key); + + case EVR_UT: // unlimited text + return new DcmUnlimitedText(key); + + case EVR_SH: // short string + return new DcmShortString(key); + + case EVR_PN: // person name + return new DcmPersonName(key); + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_UC: // unlimited characters + return new DcmUnlimitedCharacters(key); +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_UR: // URI/URL + return new DcmUniversalResourceIdentifierOrLocator(key); +#endif + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + return new DcmSignedLong(key); + + case EVR_SS: // signed short + return new DcmSignedShort(key); + + case EVR_UL: // unsigned long + return new DcmUnsignedLong(key); + + case EVR_US: // unsigned short + return new DcmUnsignedShort(key); + + case EVR_FL: // float single-precision + return new DcmFloatingPointSingle(key); + + case EVR_FD: // float double-precision + return new DcmFloatingPointDouble(key); + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * TODO + **/ + + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + + /** + * Internal to DCMTK. + **/ + + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + + throw OrthancException(ErrorCode_InternalError); + }
--- a/Resources/Graveyard/Multithreading/BagOfTasks.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/BagOfTasks.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/BagOfTasksProcessor.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/BagOfTasksProcessor.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ICommand.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/ICommand.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ILockable.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/ILockable.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/Locker.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/Locker.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/Mutex.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/Mutex.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/Mutex.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/Mutex.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ReaderWriterLock.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ReaderWriterLock.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/CallSystemCommand.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/IServerCommand.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/IServerCommand.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerJob.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ServerJob.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerJob.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ServerJob.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerScheduler.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ServerScheduler.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerScheduler.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/ServerScheduler.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StorePeerCommand.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StoreScuCommand.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/LinuxStandardBaseToolchain.cmake Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/LinuxStandardBaseToolchain.cmake Thu Mar 19 11:48:30 2020 +0100 @@ -1,4 +1,4 @@ -# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu +# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu -G Ninja INCLUDE(CMakeForceCompiler) @@ -31,7 +31,12 @@ # which compilers to use for C and C++ SET(CMAKE_C_COMPILER ${LSB_PATH}/bin/lsbcc) -CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU) + +if (${CMAKE_VERSION} VERSION_LESS "3.6.0") + CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU) +else() + SET(CMAKE_CXX_COMPILER ${LSB_PATH}/bin/lsbc++) +endif() # here is the target environment located SET(CMAKE_FIND_ROOT_PATH ${LSB_PATH})
--- a/Resources/Patches/dcmtk-3.6.2-cmath.patch Wed Mar 18 08:59:06 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -diff -urEb dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake ---- dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake 2018-09-20 09:30:34.364831213 +0200 -+++ dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake 2018-09-20 09:47:52.013660067 +0200 -@@ -568,12 +568,12 @@ - ENDIF(HAVE_CSTDDEF) - - CHECK_FUNCTIONWITHHEADER_EXISTS(feenableexcept "${HEADERS}" HAVE_PROTOTYPE_FEENABLEEXCEPT) -- CHECK_FUNCTIONWITHHEADER_EXISTS(isinf "${HEADERS}" HAVE_PROTOTYPE_ISINF) -- CHECK_FUNCTIONWITHHEADER_EXISTS(isnan "${HEADERS}" HAVE_PROTOTYPE_ISNAN) -- CHECK_FUNCTIONWITHHEADER_EXISTS(finite "${HEADERS}" HAVE_PROTOTYPE_FINITE) -- CHECK_FUNCTIONWITHHEADER_EXISTS(std::isinf "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF) -- CHECK_FUNCTIONWITHHEADER_EXISTS(std::isnan "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN) -- CHECK_FUNCTIONWITHHEADER_EXISTS(std::finite "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE) -+ CHECK_FUNCTIONWITHHEADER_EXISTS("isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISINF) -+ CHECK_FUNCTIONWITHHEADER_EXISTS("isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISNAN) -+ CHECK_FUNCTIONWITHHEADER_EXISTS("finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_FINITE) -+ CHECK_FUNCTIONWITHHEADER_EXISTS("std::isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF) -+ CHECK_FUNCTIONWITHHEADER_EXISTS("std::isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN) -+ CHECK_FUNCTIONWITHHEADER_EXISTS("std::finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE) - CHECK_FUNCTIONWITHHEADER_EXISTS(flock "${HEADERS}" HAVE_PROTOTYPE_FLOCK) - CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME) - CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname_r "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME_R)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-3.6.2.patch Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,48 @@ +diff -urEb dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake +--- dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake 2020-01-06 17:42:52.299540389 +0100 ++++ dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake 2020-01-06 17:43:56.707520036 +0100 +@@ -568,12 +568,12 @@ + ENDIF(HAVE_CSTDDEF) + + CHECK_FUNCTIONWITHHEADER_EXISTS(feenableexcept "${HEADERS}" HAVE_PROTOTYPE_FEENABLEEXCEPT) +- CHECK_FUNCTIONWITHHEADER_EXISTS(isinf "${HEADERS}" HAVE_PROTOTYPE_ISINF) +- CHECK_FUNCTIONWITHHEADER_EXISTS(isnan "${HEADERS}" HAVE_PROTOTYPE_ISNAN) +- CHECK_FUNCTIONWITHHEADER_EXISTS(finite "${HEADERS}" HAVE_PROTOTYPE_FINITE) +- CHECK_FUNCTIONWITHHEADER_EXISTS(std::isinf "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF) +- CHECK_FUNCTIONWITHHEADER_EXISTS(std::isnan "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN) +- CHECK_FUNCTIONWITHHEADER_EXISTS(std::finite "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE) ++ CHECK_FUNCTIONWITHHEADER_EXISTS("isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISINF) ++ CHECK_FUNCTIONWITHHEADER_EXISTS("isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISNAN) ++ CHECK_FUNCTIONWITHHEADER_EXISTS("finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_FINITE) ++ CHECK_FUNCTIONWITHHEADER_EXISTS("std::isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF) ++ CHECK_FUNCTIONWITHHEADER_EXISTS("std::isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN) ++ CHECK_FUNCTIONWITHHEADER_EXISTS("std::finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE) + CHECK_FUNCTIONWITHHEADER_EXISTS(flock "${HEADERS}" HAVE_PROTOTYPE_FLOCK) + CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME) + CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname_r "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME_R) +diff -urEb dcmtk-3.6.2.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.2/dcmdata/include/dcmtk/dcmdata/dcdict.h +--- dcmtk-3.6.2.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h 2020-01-06 17:42:52.283540394 +0100 ++++ dcmtk-3.6.2/dcmdata/include/dcmtk/dcmdata/dcdict.h 2020-01-06 17:46:21.711473976 +0100 +@@ -152,6 +152,12 @@ + /// returns an iterator to the end of the repeating tag dictionary + DcmDictEntryListIterator repeatingEnd() { return repDict.end(); } + ++ // Function by the Orthanc project to load a dictionary from a ++ // memory buffer, which is necessary in sandboxed ++ // environments. This is an adapted version of ++ // DcmDataDictionary::loadDictionary(). ++ OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue); ++ + private: + + /** private undefined assignment operator +diff -urEb dcmtk-3.6.2.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.2/dcmdata/libsrc/dcdict.cc +--- dcmtk-3.6.2.orig/dcmdata/libsrc/dcdict.cc 2020-01-06 17:42:52.287540392 +0100 ++++ dcmtk-3.6.2/dcmdata/libsrc/dcdict.cc 2020-01-06 17:47:18.335299472 +0100 +@@ -876,3 +876,6 @@ + wrlock().clear(); + unlock(); + } ++ ++ ++#include "dcdict_orthanc.cc"
--- a/Resources/Patches/dcmtk-3.6.4.patch Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Patches/dcmtk-3.6.4.patch Thu Mar 19 11:48:30 2020 +0100 @@ -1,6 +1,32 @@ +diff -urEb dcmtk-3.6.4.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcdict.h +--- dcmtk-3.6.4.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h 2020-01-06 19:55:12.887153062 +0100 ++++ dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcdict.h 2020-01-06 19:55:28.156447233 +0100 +@@ -152,6 +152,12 @@ + /// returns an iterator to the end of the repeating tag dictionary + DcmDictEntryListIterator repeatingEnd() { return repDict.end(); } + ++ // Function by the Orthanc project to load a dictionary from a ++ // memory buffer, which is necessary in sandboxed ++ // environments. This is an adapted version of ++ // DcmDataDictionary::loadDictionary(). ++ OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue); ++ + private: + + /** private undefined assignment operator +diff -urEb dcmtk-3.6.4.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.4/dcmdata/libsrc/dcdict.cc +--- dcmtk-3.6.4.orig/dcmdata/libsrc/dcdict.cc 2020-01-06 19:55:12.899154075 +0100 ++++ dcmtk-3.6.4/dcmdata/libsrc/dcdict.cc 2020-01-06 19:55:28.156447233 +0100 +@@ -899,3 +899,6 @@ + wrlock().clear(); + wrunlock(); + } ++ ++ ++#include "dcdict_orthanc.cc" diff -urEb dcmtk-3.6.4.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-3.6.4/dcmdata/libsrc/dcpxitem.cc ---- dcmtk-3.6.4.orig/dcmdata/libsrc/dcpxitem.cc 2019-02-21 15:30:21.657110805 +0100 -+++ dcmtk-3.6.4/dcmdata/libsrc/dcpxitem.cc 2019-02-21 16:28:43.721049550 +0100 +--- dcmtk-3.6.4.orig/dcmdata/libsrc/dcpxitem.cc 2020-01-06 19:55:12.899154075 +0100 ++++ dcmtk-3.6.4/dcmdata/libsrc/dcpxitem.cc 2020-01-06 19:55:28.156447233 +0100 @@ -36,6 +36,9 @@ #include "dcmtk/dcmdata/dcostrma.h" /* for class DcmOutputStream */ #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */ @@ -11,9 +37,57 @@ // ******************************** +diff -urEb dcmtk-3.6.4.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-3.6.4/oflog/include/dcmtk/oflog/thread/syncpub.h +--- dcmtk-3.6.4.orig/oflog/include/dcmtk/oflog/thread/syncpub.h 2020-01-06 19:55:12.911155088 +0100 ++++ dcmtk-3.6.4/oflog/include/dcmtk/oflog/thread/syncpub.h 2020-01-06 19:56:26.991372656 +0100 +@@ -63,7 +63,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + Mutex::Mutex (Mutex::Type t) +- : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t)) + 0) ++ : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t))) + { } + + +@@ -106,7 +106,7 @@ + DCMTK_LOG4CPLUS_INLINE_EXPORT + Semaphore::Semaphore (unsigned DCMTK_LOG4CPLUS_THREADED (max), + unsigned DCMTK_LOG4CPLUS_THREADED (initial)) +- : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial)) + 0) ++ : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial))) + { } + + +@@ -148,7 +148,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + FairMutex::FairMutex () +- : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex) + 0) ++ : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex)) + { } + + +@@ -190,7 +190,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + ManualResetEvent::ManualResetEvent (bool DCMTK_LOG4CPLUS_THREADED (sig)) +- : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig)) + 0) ++ : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig))) + { } + + +@@ -252,7 +252,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + SharedMutex::SharedMutex () +- : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex) + 0) ++ : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex)) + { } + + diff -urEb dcmtk-3.6.4.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.4/ofstd/include/dcmtk/ofstd/offile.h ---- dcmtk-3.6.4.orig/ofstd/include/dcmtk/ofstd/offile.h 2019-02-21 15:30:21.645110805 +0100 -+++ dcmtk-3.6.4/ofstd/include/dcmtk/ofstd/offile.h 2019-02-21 15:30:48.273110339 +0100 +--- dcmtk-3.6.4.orig/ofstd/include/dcmtk/ofstd/offile.h 2020-01-06 19:55:12.951158464 +0100 ++++ dcmtk-3.6.4/ofstd/include/dcmtk/ofstd/offile.h 2020-01-06 19:55:28.156447233 +0100 @@ -575,7 +575,7 @@ */ void setlinebuf() @@ -23,4 +97,3 @@ this->setvbuf(NULL, _IOLBF, 0); #else :: setlinebuf(file_); -Only in dcmtk-3.6.4/ofstd/include/dcmtk/ofstd: offile.h~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-3.6.5.patch Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,113 @@ +diff -urEb dcmtk-3.6.5.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.5/dcmdata/include/dcmtk/dcmdata/dcdict.h +--- dcmtk-3.6.5.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h 2020-03-18 10:22:41.555166774 +0100 ++++ dcmtk-3.6.5/dcmdata/include/dcmtk/dcmdata/dcdict.h 2020-03-18 10:22:53.395131056 +0100 +@@ -152,6 +152,12 @@ + /// returns an iterator to the end of the repeating tag dictionary + DcmDictEntryListIterator repeatingEnd() { return repDict.end(); } + ++ // Function by the Orthanc project to load a dictionary from a ++ // memory buffer, which is necessary in sandboxed ++ // environments. This is an adapted version of ++ // DcmDataDictionary::loadDictionary(). ++ OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue); ++ + private: + + /** private undefined assignment operator +diff -urEb dcmtk-3.6.5.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.5/dcmdata/libsrc/dcdict.cc +--- dcmtk-3.6.5.orig/dcmdata/libsrc/dcdict.cc 2020-03-18 10:22:41.559166762 +0100 ++++ dcmtk-3.6.5/dcmdata/libsrc/dcdict.cc 2020-03-18 10:22:53.395131056 +0100 +@@ -900,3 +900,6 @@ + wrlock().clear(); + wrunlock(); + } ++ ++ ++#include "dcdict_orthanc.cc" +diff -urEb dcmtk-3.6.5.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-3.6.5/dcmdata/libsrc/dcpxitem.cc +--- dcmtk-3.6.5.orig/dcmdata/libsrc/dcpxitem.cc 2020-03-18 10:22:41.559166762 +0100 ++++ dcmtk-3.6.5/dcmdata/libsrc/dcpxitem.cc 2020-03-18 10:22:53.395131056 +0100 +@@ -36,6 +36,9 @@ + #include "dcmtk/dcmdata/dcostrma.h" /* for class DcmOutputStream */ + #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */ + ++#undef max ++#include "dcmtk/ofstd/oflimits.h" ++ + + // ******************************** + +diff -urEb dcmtk-3.6.5.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-3.6.5/oflog/include/dcmtk/oflog/thread/syncpub.h +--- dcmtk-3.6.5.orig/oflog/include/dcmtk/oflog/thread/syncpub.h 2020-03-18 10:22:41.543166810 +0100 ++++ dcmtk-3.6.5/oflog/include/dcmtk/oflog/thread/syncpub.h 2020-03-18 10:22:53.395131056 +0100 +@@ -63,7 +63,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + Mutex::Mutex (Mutex::Type t) +- : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t)) + 0) ++ : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t))) + { } + + +@@ -106,7 +106,7 @@ + DCMTK_LOG4CPLUS_INLINE_EXPORT + Semaphore::Semaphore (unsigned DCMTK_LOG4CPLUS_THREADED (max), + unsigned DCMTK_LOG4CPLUS_THREADED (initial)) +- : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial)) + 0) ++ : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial))) + { } + + +@@ -148,7 +148,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + FairMutex::FairMutex () +- : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex) + 0) ++ : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex)) + { } + + +@@ -190,7 +190,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + ManualResetEvent::ManualResetEvent (bool DCMTK_LOG4CPLUS_THREADED (sig)) +- : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig)) + 0) ++ : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig))) + { } + + +@@ -252,7 +252,7 @@ + + DCMTK_LOG4CPLUS_INLINE_EXPORT + SharedMutex::SharedMutex () +- : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex) + 0) ++ : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex)) + { } + + +diff -urEb dcmtk-3.6.5.orig/oflog/libsrc/oflog.cc dcmtk-3.6.5/oflog/libsrc/oflog.cc +--- dcmtk-3.6.5.orig/oflog/libsrc/oflog.cc 2020-03-18 10:22:41.547166798 +0100 ++++ dcmtk-3.6.5/oflog/libsrc/oflog.cc 2020-03-18 11:55:50.116856932 +0100 +@@ -19,6 +19,10 @@ + * + */ + ++#if defined(_WIN32) ++# include <winsock2.h> ++#endif ++ + #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ + #include "dcmtk/oflog/oflog.h" + +diff -urEb dcmtk-3.6.5.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.5/ofstd/include/dcmtk/ofstd/offile.h +--- dcmtk-3.6.5.orig/ofstd/include/dcmtk/ofstd/offile.h 2020-03-18 10:22:41.587166677 +0100 ++++ dcmtk-3.6.5/ofstd/include/dcmtk/ofstd/offile.h 2020-03-18 10:22:53.395131056 +0100 +@@ -575,7 +575,7 @@ + */ + void setlinebuf() + { +-#if defined(_WIN32) || defined(__hpux) ++#if defined(_WIN32) || defined(__hpux) || defined(__LSB_VERSION__) + this->setvbuf(NULL, _IOLBF, 0); + #else + :: setlinebuf(file_);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-dcdict_orthanc.cc Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,205 @@ +// Function by the Orthanc project to load a dictionary from a memory +// buffer, which is necessary in sandboxed environments. This is an +// adapted version of DcmDataDictionary::loadDictionary(). + +#include <string> +#include <boost/noncopyable.hpp> + +struct OrthancLinesIterator; + +// This plain old C class is implemented in "../../Core/Toolbox.h" +OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content); + +bool OrthancLinesIterator_GetLine(std::string& target, + const OrthancLinesIterator* iterator); + +void OrthancLinesIterator_Next(OrthancLinesIterator* iterator); + +void OrthancLinesIterator_Free(OrthancLinesIterator* iterator); + + +class LinesIterator : public boost::noncopyable +{ +private: + OrthancLinesIterator* iterator_; + +public: + LinesIterator(const std::string& content) : + iterator_(NULL) + { + iterator_ = OrthancLinesIterator_Create(content); + } + + ~LinesIterator() + { + if (iterator_ != NULL) + { + OrthancLinesIterator_Free(iterator_); + iterator_ = NULL; + } + } + + bool GetLine(std::string& target) const + { + if (iterator_ != NULL) + { + return OrthancLinesIterator_GetLine(target, iterator_); + } + else + { + return false; + } + } + + void Next() + { + if (iterator_ != NULL) + { + OrthancLinesIterator_Next(iterator_); + } + } +}; + + + +OFBool +DcmDataDictionary::loadFromMemory(const std::string& content, OFBool errorIfAbsent) +{ + int lineNumber = 0; + char* lineFields[DCM_MAXDICTFIELDS + 1]; + int fieldsPresent; + DcmDictEntry* e; + int errorsEncountered = 0; + OFBool errorOnThisLine = OFFalse; + int i; + + DcmTagKey key, upperKey; + DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified; + DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified; + DcmVR vr; + char* vrName; + char* tagName; + char* privCreator; + int vmMin, vmMax = 1; + const char* standardVersion; + + LinesIterator iterator(content); + + std::string line; + while (iterator.GetLine(line)) { + iterator.Next(); + + if (line.size() >= DCM_MAXDICTLINESIZE) { + DCMDATA_ERROR("DcmDataDictionary: Too long line: " << line); + continue; + } + + lineNumber++; + + if (onlyWhitespace(line.c_str())) { + continue; /* ignore this line */ + } + if (isaCommentLine(line.c_str())) { + continue; /* ignore this line */ + } + + errorOnThisLine = OFFalse; + + /* fields are tab separated */ + fieldsPresent = splitFields(line.c_str(), lineFields, + DCM_MAXDICTFIELDS, + DCM_DICT_FIELD_SEPARATOR_CHAR); + + /* initialize dict entry fields */ + vrName = NULL; + tagName = NULL; + privCreator = NULL; + vmMin = vmMax = 1; + standardVersion = "DICOM"; + + switch (fieldsPresent) { + case 0: + case 1: + case 2: + DCMDATA_ERROR("DcmDataDictionary: " + << "too few fields (line " << lineNumber << ")"); + errorOnThisLine = OFTrue; + break; + default: + DCMDATA_ERROR("DcmDataDictionary: " + << "too many fields (line " << lineNumber << "): "); + errorOnThisLine = OFTrue; + break; + case 5: + stripWhitespace(lineFields[4]); + standardVersion = lineFields[4]; + /* drop through to next case label */ + case 4: + /* the VM field is present */ + if (!parseVMField(lineFields[3], vmMin, vmMax)) { + DCMDATA_ERROR("DcmDataDictionary: " + << "bad VM field (line " << lineNumber << "): " << lineFields[3]); + errorOnThisLine = OFTrue; + } + /* drop through to next case label */ + case 3: + if (!parseWholeTagField(lineFields[0], key, upperKey, + groupRestriction, elementRestriction, privCreator)) + { + DCMDATA_ERROR("DcmDataDictionary: " + << "bad Tag field (line " << lineNumber << "): " << lineFields[0]); + errorOnThisLine = OFTrue; + } else { + /* all is OK */ + vrName = lineFields[1]; + stripWhitespace(vrName); + + tagName = lineFields[2]; + stripWhitespace(tagName); + } + } + + if (!errorOnThisLine) { + /* check the VR Field */ + vr.setVR(vrName); + if (vr.getEVR() == EVR_UNKNOWN) { + DCMDATA_ERROR("DcmDataDictionary: " + << "bad VR field (line " << lineNumber << "): " << vrName); + errorOnThisLine = OFTrue; + } + } + + if (!errorOnThisLine) { + e = new DcmDictEntry( + key.getGroup(), key.getElement(), + upperKey.getGroup(), upperKey.getElement(), + vr, tagName, vmMin, vmMax, standardVersion, OFTrue, + privCreator); + + e->setGroupRangeRestriction(groupRestriction); + e->setElementRangeRestriction(elementRestriction); + addEntry(e); + } + + for (i = 0; i < fieldsPresent; i++) { + free(lineFields[i]); + lineFields[i] = NULL; + } + + delete[] privCreator; + + if (errorOnThisLine) { + errorsEncountered++; + } + } + + /* return OFFalse in case of errors and set internal state accordingly */ + if (errorsEncountered == 0) { + dictionaryLoaded = OFTrue; + return OFTrue; + } + else { + dictionaryLoaded = OFFalse; + return OFFalse; + } +}
--- a/Resources/Patches/dcmtk.txt Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Patches/dcmtk.txt Thu Mar 19 11:48:30 2020 +0100 @@ -4,6 +4,7 @@ diff -urEb dcmtk-3.6.0.orig/ dcmtk-3.6.0 diff -urEb dcmtk-3.6.2.orig/ dcmtk-3.6.2 diff -urEb dcmtk-3.6.4.orig/ dcmtk-3.6.4 +diff -urEb dcmtk-3.6.5.orig/ dcmtk-3.6.5 For "dcmtk-3.6.2-private.dic" =============================
--- a/Resources/Patches/libp11-0.4.0.patch Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Patches/libp11-0.4.0.patch Thu Mar 19 11:48:30 2020 +0100 @@ -1,6 +1,6 @@ diff -urEb libp11-0.4.0.orig/src/atfork.c libp11-0.4.0/src/atfork.c ---- libp11-0.4.0.orig/src/atfork.c 2016-06-20 13:38:43.845575107 +0200 -+++ libp11-0.4.0/src/atfork.c 2016-06-20 13:46:52.969575591 +0200 +--- libp11-0.4.0.orig/src/atfork.c 2020-03-05 20:48:55.447852662 +0100 ++++ libp11-0.4.0/src/atfork.c 2020-03-05 20:49:05.983770656 +0100 @@ -25,7 +25,7 @@ #include <sys/stat.h> #include <sys/types.h> @@ -11,8 +11,8 @@ #ifdef __sun # pragma fini(lib_deinit) diff -urEb libp11-0.4.0.orig/src/engine.h libp11-0.4.0/src/engine.h ---- libp11-0.4.0.orig/src/engine.h 2016-06-20 13:38:43.845575107 +0200 -+++ libp11-0.4.0/src/engine.h 2016-06-20 13:46:27.421575566 +0200 +--- libp11-0.4.0.orig/src/engine.h 2020-03-05 20:48:55.447852662 +0100 ++++ libp11-0.4.0/src/engine.h 2020-03-05 20:49:05.983770656 +0100 @@ -29,7 +29,7 @@ #define _ENGINE_PKCS11_H @@ -23,8 +23,8 @@ #include "libp11.h" diff -urEb libp11-0.4.0.orig/src/libp11-int.h libp11-0.4.0/src/libp11-int.h ---- libp11-0.4.0.orig/src/libp11-int.h 2016-06-20 13:38:43.845575107 +0200 -+++ libp11-0.4.0/src/libp11-int.h 2016-06-20 13:46:27.421575566 +0200 +--- libp11-0.4.0.orig/src/libp11-int.h 2020-03-05 20:48:55.447852662 +0100 ++++ libp11-0.4.0/src/libp11-int.h 2020-03-05 20:49:05.983770656 +0100 @@ -20,7 +20,7 @@ #define _LIBP11_INT_H @@ -34,3 +34,31 @@ #endif #include "libp11.h" +diff -urEb libp11-0.4.0.orig/src/p11_key.c libp11-0.4.0/src/p11_key.c +--- libp11-0.4.0.orig/src/p11_key.c 2020-03-05 20:48:55.447852662 +0100 ++++ libp11-0.4.0/src/p11_key.c 2020-03-05 20:49:24.959625180 +0100 +@@ -21,6 +21,10 @@ + #include <string.h> + #include <openssl/bn.h> + ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.0.2 ++# include <crypto/rsa/rsa_locl.h> ++#endif ++ + #ifdef _WIN32 + #define strncasecmp strnicmp + #endif +diff -urEb libp11-0.4.0.orig/src/p11_rsa.c libp11-0.4.0/src/p11_rsa.c +--- libp11-0.4.0.orig/src/p11_rsa.c 2020-03-05 20:48:55.447852662 +0100 ++++ libp11-0.4.0/src/p11_rsa.c 2020-03-05 20:49:20.095662204 +0100 +@@ -27,6 +27,10 @@ + #include <openssl/evp.h> + #include <openssl/rsa.h> + ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.0.2 ++# include <crypto/rsa/rsa_locl.h> ++#endif ++ + static int rsa_ex_index = 0; + + #if OPENSSL_VERSION_NUMBER < 0x10100003L
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/openssl-1.1.1d-conf.h.in Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,122 @@ +/* + * {- join("\n * ", @autowarntext) -} + * + * Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include <openssl/opensslv.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef OPENSSL_ALGORITHM_DEFINES +# error OPENSSL_ALGORITHM_DEFINES no longer supported +#endif + + +/* + * Sometimes OPENSSSL_NO_xxx ends up with an empty file and some compilers + * don't like that. This will hopefully silence them. + */ +#define NON_EMPTY_TRANSLATION_UNIT static void *dummy = &dummy; + +/* + * Applications should use -DOPENSSL_API_COMPAT=<version> to suppress the + * declarations of functions deprecated in or before <version>. Otherwise, they + * still won't see them if the library has been built to disable deprecated + * functions. + */ +#ifndef DECLARE_DEPRECATED +# define DECLARE_DEPRECATED(f) f; +# ifdef __GNUC__ +# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 0) +# undef DECLARE_DEPRECATED +# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated)); +# endif +# endif +#endif + +#ifndef OPENSSL_FILE +# ifdef OPENSSL_NO_FILENAMES +# define OPENSSL_FILE "" +# define OPENSSL_LINE 0 +# else +# define OPENSSL_FILE __FILE__ +# define OPENSSL_LINE __LINE__ +# endif +#endif + +#ifndef OPENSSL_MIN_API +# define OPENSSL_MIN_API 0 +#endif + +#if !defined(OPENSSL_API_COMPAT) || OPENSSL_API_COMPAT < OPENSSL_MIN_API +# undef OPENSSL_API_COMPAT +# define OPENSSL_API_COMPAT OPENSSL_MIN_API +#endif + +/* + * Do not deprecate things to be deprecated in version 1.2.0 before the + * OpenSSL version number matches. + */ +#if OPENSSL_VERSION_NUMBER < 0x10200000L +# define DEPRECATEDIN_1_2_0(f) f; +#elif OPENSSL_API_COMPAT < 0x10200000L +# define DEPRECATEDIN_1_2_0(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_1_2_0(f) +#endif + +#if OPENSSL_API_COMPAT < 0x10100000L +# define DEPRECATEDIN_1_1_0(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_1_1_0(f) +#endif + +#if OPENSSL_API_COMPAT < 0x10000000L +# define DEPRECATEDIN_1_0_0(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_1_0_0(f) +#endif + +#if OPENSSL_API_COMPAT < 0x00908000L +# define DEPRECATEDIN_0_9_8(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_0_9_8(f) +#endif + + +#define OPENSSL_UNISTD <unistd.h> + +#if 0 +/* Generate 80386 code? */ +{- ${processor} eq "386" ? "#define" : "#undef" -} I386_ONLY + +#undef OPENSSL_UNISTD +#define OPENSSL_UNISTD {- ${unistd} -} + +{- ${export_var_as_fn} ? "#define" : "#undef" -} OPENSSL_EXPORT_VAR_AS_FUNCTION + +/* + * The following are cipher-specific, but are part of the public API. + */ +#if !defined(OPENSSL_SYS_UEFI) +{- ${bn_ll} ? "# define" : "# undef" -} BN_LLONG +/* Only one for the following should be defined */ +{- ${b64l} ? "# define" : "# undef" -} SIXTY_FOUR_BIT_LONG +{- ${b64} ? "# define" : "# undef" -} SIXTY_FOUR_BIT +{- ${b32} ? "# define" : "# undef" -} THIRTY_TWO_BIT +#endif + +#define RC4_INT {- ${rc4_int} -} +#endif + +#ifdef __cplusplus +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/openssl-1.1.1d.patch Thu Mar 19 11:48:30 2020 +0100 @@ -0,0 +1,12 @@ +diff -urEb openssl-1.1.1d.orig/crypto/rand/rand_unix.c openssl-1.1.1d/crypto/rand/rand_unix.c +--- openssl-1.1.1d.orig/crypto/rand/rand_unix.c 2019-09-10 15:13:07.000000000 +0200 ++++ openssl-1.1.1d/crypto/rand/rand_unix.c 2020-03-05 16:29:33.030136203 +0100 +@@ -340,7 +340,7 @@ + # endif + + /* Linux supports this since version 3.17 */ +-# if defined(__linux) && defined(__NR_getrandom) ++# if defined(__linux) && defined(__NR_getrandom) && !defined(__LSB_VERSION__) + return syscall(__NR_getrandom, buf, buflen, 0); + # elif (defined(__FreeBSD__) || defined(__NetBSD__)) && defined(KERN_ARND) + return sysctl_random(buf, buflen);
--- a/Resources/RetrieveCACertificates.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/RetrieveCACertificates.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/CppHelpers/Logging/ILogger.h Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/CppHelpers/Logging/ILogger.h Thu Mar 19 11:48:30 2020 +0100 @@ -8,6 +8,21 @@ namespace OrthancHelpers { + + inline std::string ShortenId(const std::string& orthancUuid) + { + size_t firstHyphenPos = orthancUuid.find_first_of('-'); + if (firstHyphenPos == std::string::npos) + { + return orthancUuid; + } + else + { + return orthancUuid.substr(0, firstHyphenPos); + } + } + + // Interface for loggers providing the same interface // in Orthanc framework or in an Orthanc plugins. // Furthermore, compared to the LOG and VLOG macros, @@ -25,9 +40,9 @@ virtual void Error(const char* message) = 0; virtual void Error(const std::string& message) = 0; - virtual void EnterContext(const char* message) = 0; - virtual void EnterContext(const std::string& message) = 0; - virtual void LeaveContext() = 0; + virtual void EnterContext(const char* message, bool forceLogContextChange = false) = 0; + virtual void EnterContext(const std::string& message, bool forceLogContextChange = false) = 0; + virtual void LeaveContext(bool forceLogContextChange = false) = 0; }; @@ -55,12 +70,12 @@ logContextChanges_ = enable; } - virtual void EnterContext(const char* message) + virtual void EnterContext(const char* message, bool forceLogContextChange = false) { - EnterContext(std::string(message)); + EnterContext(std::string(message), forceLogContextChange); } - virtual void EnterContext(const std::string& message) + virtual void EnterContext(const std::string& message, bool forceLogContextChange = false) { if (!contexts_.get()) { @@ -68,15 +83,15 @@ } contexts_->push_back(message); - if (logContextChanges_) + if (logContextChanges_ || forceLogContextChange) { Info(".. entering"); } } - virtual void LeaveContext() + virtual void LeaveContext(bool forceLogContextChange = false) { - if (logContextChanges_) + if (logContextChanges_ || forceLogContextChange) { Info(".. leaving"); } @@ -128,22 +143,25 @@ class LogContext { ILogger* logger_; + bool forceLogContextChange_; public: - LogContext(ILogger* logger, const char* context) : - logger_(logger) + LogContext(ILogger* logger, const char* context, bool forceLogContextChange = false) : + logger_(logger), + forceLogContextChange_(forceLogContextChange) { - logger_->EnterContext(context); + logger_->EnterContext(context, forceLogContextChange_); } - LogContext(ILogger* logger, const std::string& context) : - logger_(logger) + LogContext(ILogger* logger, const std::string& context, bool forceLogContextChange = false) : + logger_(logger), + forceLogContextChange_(forceLogContextChange) { - logger_->EnterContext(context); + logger_->EnterContext(context, forceLogContextChange_); } ~LogContext() { - logger_->LeaveContext(); + logger_->LeaveContext(forceLogContextChange_); } };
--- a/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Lua/CallWebService.js Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Lua/CallWebService.js Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Lua/TransferSyntaxDisable.lua Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Lua/TransferSyntaxDisable.lua Thu Mar 19 11:48:30 2020 +0100 @@ -22,6 +22,10 @@ return false end +function IsMpeg4TransferSyntaxAccepted(aet, ip) + return false +end + function IsRleTransferSyntaxAccepted(aet, ip) return false end
--- a/Resources/Samples/Lua/TransferSyntaxEnable.lua Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Lua/TransferSyntaxEnable.lua Thu Mar 19 11:48:30 2020 +0100 @@ -22,6 +22,10 @@ return true end +function IsMpeg4TransferSyntaxAccepted(aet, ip) + return true +end + function IsRleTransferSyntaxAccepted(aet, ip) return true end
--- a/Resources/Samples/Python/AnonymizeAllPatients.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/AnonymizeAllPatients.py Thu Mar 19 11:48:30 2020 +0100 @@ -4,7 +4,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ArchiveAllPatients.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/ArchiveAllPatients.py Thu Mar 19 11:48:30 2020 +0100 @@ -4,7 +4,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ArchiveStudiesInTimeRange.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/ArchiveStudiesInTimeRange.py Thu Mar 19 11:48:30 2020 +0100 @@ -4,7 +4,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/AutoClassify.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/AutoClassify.py Thu Mar 19 11:48:30 2020 +0100 @@ -4,7 +4,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ChangesLoop.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/ChangesLoop.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ContinuousPatientAnonymization.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/ContinuousPatientAnonymization.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/DeleteAllStudies.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/DeleteAllStudies.py Thu Mar 19 11:48:30 2020 +0100 @@ -4,7 +4,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/DownloadAnonymized.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/DownloadAnonymized.py Thu Mar 19 11:48:30 2020 +0100 @@ -4,7 +4,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/HighPerformanceAutoRouting.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py Thu Mar 19 11:48:30 2020 +0100 @@ -4,7 +4,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ManualModification.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/ManualModification.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/Replicate.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/Replicate.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/RestToolbox.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Python/RestToolbox.py Thu Mar 19 11:48:30 2020 +0100 @@ -1,7 +1,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Tools/RecoverCompressedFile.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/Tools/RecoverCompressedFile.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/WebApplications/DrawingDicomizer.js Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/WebApplications/DrawingDicomizer.js Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/WebApplications/NodeToolbox.js Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/Samples/WebApplications/NodeToolbox.js Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/Resources/WebAssembly/dcdict.cc Wed Mar 18 08:59:06 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1087 +0,0 @@ -/* - * - * Copyright (C) 1994-2016, OFFIS e.V. - * All rights reserved. See COPYRIGHT file for details. - * - * This software and supporting documentation were developed by - * - * OFFIS e.V. - * R&D Division Health - * Escherweg 2 - * D-26121 Oldenburg, Germany - * - * - * Module: dcmdata - * - * Author: Andrew Hewett - * - * Purpose: loadable DICOM data dictionary - * - */ - - -#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ - -#include "dcmtk/ofstd/ofstd.h" -#include "dcmtk/dcmdata/dcdict.h" -#include "dcmtk/ofstd/ofdefine.h" -#include "dcmtk/dcmdata/dcdicent.h" -#include "dcmtk/dcmdata/dctypes.h" - -#define INCLUDE_CSTDLIB -#define INCLUDE_CSTDIO -#define INCLUDE_CSTRING -#define INCLUDE_CCTYPE -#include "dcmtk/ofstd/ofstdinc.h" - -/* -** The separator character between fields in the data dictionary file(s) -*/ -#define DCM_DICT_FIELD_SEPARATOR_CHAR '\t' - -/* -** Comment character for the data dictionary file(s) -*/ -#define DCM_DICT_COMMENT_CHAR '#' - -/* -** THE Global DICOM Data Dictionary -*/ - -GlobalDcmDataDictionary dcmDataDict; - - -/* -** Member Functions -*/ - -static DcmDictEntry* -makeSkelEntry(Uint16 group, Uint16 element, - Uint16 upperGroup, Uint16 upperElement, - DcmEVR evr, const char* tagName, int vmMin, int vmMax, - const char* standardVersion, - DcmDictRangeRestriction groupRestriction, - DcmDictRangeRestriction elementRestriction, - const char* privCreator) -{ - DcmDictEntry* e = NULL; - e = new DcmDictEntry(group, element, upperGroup, upperElement, evr, - tagName, vmMin, vmMax, standardVersion, OFFalse, privCreator); - if (e != NULL) { - e->setGroupRangeRestriction(groupRestriction); - e->setElementRangeRestriction(elementRestriction); - } - return e; -} - - -OFBool DcmDataDictionary::loadSkeletonDictionary() -{ - /* - ** We need to know about Group Lengths to compute them - */ - DcmDictEntry* e = NULL; - e = makeSkelEntry(0x0000, 0x0000, 0xffff, 0x0000, - EVR_UL, "GenericGroupLength", 1, 1, "GENERIC", - DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); - addEntry(e); - - /* - ** We need to know about Items and Delimitation Items to parse - ** (and construct) sequences. - */ - e = makeSkelEntry(0xfffe, 0xe000, 0xfffe, 0xe000, - EVR_na, "Item", 1, 1, "DICOM", - DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); - addEntry(e); - e = makeSkelEntry(0xfffe, 0xe00d, 0xfffe, 0xe00d, - EVR_na, "ItemDelimitationItem", 1, 1, "DICOM", - DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); - addEntry(e); - e = makeSkelEntry(0xfffe, 0xe0dd, 0xfffe, 0xe0dd, - EVR_na, "SequenceDelimitationItem", 1, 1, "DICOM", - DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); - addEntry(e); - - skeletonCount = numberOfEntries(); - return OFTrue; -} - - -DcmDataDictionary::DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal) - : hashDict(), - repDict(), - skeletonCount(0), - dictionaryLoaded(OFFalse) -{ - reloadDictionaries(loadBuiltin, loadExternal); -} - -DcmDataDictionary::~DcmDataDictionary() -{ - clear(); -} - - -void DcmDataDictionary::clear() -{ - hashDict.clear(); - repDict.clear(); - skeletonCount = 0; - dictionaryLoaded = OFFalse; -} - - -static void -stripWhitespace(char* s) -{ - if (s) - { - unsigned char c; - unsigned char *t; - unsigned char *p; - t=p=OFreinterpret_cast(unsigned char *, s); - while ((c = *t++)) if (!isspace(c)) *p++ = c; - *p = '\0'; - } -} - -static char* -stripTrailingWhitespace(char* s) -{ - if (s == NULL) return s; - for - ( - char* it = s + strlen(s) - 1; - it >= s && isspace(OFstatic_cast(unsigned char, *it)); - *it-- = '\0' - ); - return s; -} - -static void -stripLeadingWhitespace(char* s) -{ - if (s) - { - unsigned char c; - unsigned char *t; - unsigned char *p; - t=p=OFreinterpret_cast(unsigned char *, s); - while (isspace(*t)) t++; - while ((c = *t++)) *p++ = c; - *p = '\0'; - } -} - -static OFBool -parseVMField(char* vmField, int& vmMin, int& vmMax) -{ - OFBool ok = OFTrue; - char c = 0; - int dummy = 0; - - /* strip any whitespace */ - stripWhitespace(vmField); - - if (sscanf(vmField, "%d-%d%c", &vmMin, &dummy, &c) == 3) { - /* treat "2-2n" like "2-n" for the moment */ - if ((c == 'n') || (c == 'N')) { - vmMax = DcmVariableVM; - } else { - ok = OFFalse; - } - } else if (sscanf(vmField, "%d-%d", &vmMin, &vmMax) == 2) { - /* range VM (e.g. "2-6") */ - } else if (sscanf(vmField, "%d-%c", &vmMin, &c) == 2) { - if ((c == 'n') || (c == 'N')) { - vmMax = DcmVariableVM; - } else { - ok = OFFalse; - } - } else if (sscanf(vmField, "%d%c", &vmMin, &c) == 2) { - /* treat "2n" like "2-n" for the moment */ - if ((c == 'n') || (c == 'N')) { - vmMax = DcmVariableVM; - } else { - ok = OFFalse; - } - } else if (sscanf(vmField, "%d", &vmMin) == 1) { - /* fixed VM */ - vmMax = vmMin; - } else if (sscanf(vmField, "%c", &c) == 1) { - /* treat "n" like "1-n" */ - if ((c == 'n') || (c == 'N')) { - vmMin = 1; - vmMax = DcmVariableVM; - } else { - ok = OFFalse; - } - } else { - ok = OFFalse; - } - return ok; -} - -static int -splitFields(const char* line, char* fields[], int maxFields, char splitChar) -{ - const char *p; - int foundFields = 0; - size_t len; - - do { -#ifdef __BORLANDC__ - // Borland Builder expects a non-const argument - p = strchr(OFconst_cast(char *, line), splitChar); -#else - p = strchr(line, splitChar); -#endif - if (p == NULL) { - len = strlen(line); - } else { - len = p - line; - } - fields[foundFields] = OFstatic_cast(char *, malloc(len + 1)); - strncpy(fields[foundFields], line, len); - fields[foundFields][len] = '\0'; - foundFields++; - line = p + 1; - } while ((foundFields < maxFields) && (p != NULL)); - - return foundFields; -} - -static OFBool -parseTagPart(char *s, unsigned int& l, unsigned int& h, - DcmDictRangeRestriction& r) -{ - OFBool ok = OFTrue; - char restrictor = ' '; - - r = DcmDictRange_Unspecified; /* by default */ - - if (sscanf(s, "%x-%c-%x", &l, &restrictor, &h) == 3) { - switch (restrictor) { - case 'o': - case 'O': - r = DcmDictRange_Odd; - break; - case 'e': - case 'E': - r = DcmDictRange_Even; - break; - case 'u': - case 'U': - r = DcmDictRange_Unspecified; - break; - default: - DCMDATA_ERROR("DcmDataDictionary: Unknown range restrictor: " << restrictor); - ok = OFFalse; - break; - } - } else if (sscanf(s, "%x-%x", &l, &h) == 2) { - r = DcmDictRange_Even; /* by default */ - } else if (sscanf(s, "%x", &l) == 1) { - h = l; - } else { - ok = OFFalse; - } - return ok; -} - -static OFBool -parseWholeTagField(char* s, DcmTagKey& key, - DcmTagKey& upperKey, - DcmDictRangeRestriction& groupRestriction, - DcmDictRangeRestriction& elementRestriction, - char *&privCreator) -{ - unsigned int gl, gh, el, eh; - groupRestriction = DcmDictRange_Unspecified; - elementRestriction = DcmDictRange_Unspecified; - - stripLeadingWhitespace(s); - stripTrailingWhitespace(s); - - char gs[64]; - char es[64]; - char pc[64]; - size_t slen = strlen(s); - - if (s[0] != '(') return OFFalse; - if (s[slen - 1] != ')') return OFFalse; - if (strchr(s, ',') == NULL) return OFFalse; - - /* separate the group and element parts */ - int i = 1; /* after the '(' */ - int gi = 0; - for (; s[i] != ',' && s[i] != '\0'; i++) - { - gs[gi] = s[i]; - gi++; - } - gs[gi] = '\0'; - - if (s[i] == '\0') return OFFalse; /* element part missing */ - i++; /* after the ',' */ - - stripLeadingWhitespace(s + i); - - int pi = 0; - if (s[i] == '\"') /* private creator */ - { - i++; // skip opening quotation mark - for (; s[i] != '\"' && s[i] != '\0'; i++) pc[pi++] = s[i]; - pc[pi] = '\0'; - if (s[i] == '\0') return OFFalse; /* closing quotation mark missing */ - i++; - stripLeadingWhitespace(s + i); - if (s[i] != ',') return OFFalse; /* element part missing */ - i++; /* after the ',' */ - } - - int ei = 0; - for (; s[i] != ')' && s[i] != '\0'; i++) { - es[ei] = s[i]; - ei++; - } - es[ei] = '\0'; - - /* parse the tag parts into their components */ - stripWhitespace(gs); - if (parseTagPart(gs, gl, gh, groupRestriction) == OFFalse) - return OFFalse; - - stripWhitespace(es); - if (parseTagPart(es, el, eh, elementRestriction) == OFFalse) - return OFFalse; - - if (pi > 0) - { - // copy private creator name - privCreator = new char[strlen(pc) + 1]; // deleted by caller - if (privCreator) strcpy(privCreator,pc); - } - - key.set(OFstatic_cast(unsigned short, gl), OFstatic_cast(unsigned short, el)); - upperKey.set(OFstatic_cast(unsigned short, gh), OFstatic_cast(unsigned short, eh)); - - return OFTrue; -} - -static OFBool -onlyWhitespace(const char* s) -{ - size_t len = strlen(s); - int charsFound = OFFalse; - - for (size_t i = 0; (!charsFound) && (i < len); ++i) { - charsFound = !isspace(OFstatic_cast(unsigned char, s[i])); - } - return (!charsFound)? (OFTrue) : (OFFalse); -} - -static char* -getLine(char* line, int maxLineLen, FILE* f) -{ - char* s; - - s = fgets(line, maxLineLen, f); - - /* strip any trailing white space */ - stripTrailingWhitespace(line); - - return s; -} - -static OFBool -isaCommentLine(const char* s) -{ - OFBool isComment = OFFalse; /* assumption */ - size_t len = strlen(s); - size_t i = 0; - for (i = 0; i < len && isspace(OFstatic_cast(unsigned char, s[i])); ++i) /*loop*/; - isComment = (s[i] == DCM_DICT_COMMENT_CHAR); - return isComment; -} - -OFBool -DcmDataDictionary::reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal) -{ - OFBool result = OFTrue; - clear(); - loadSkeletonDictionary(); - if (loadBuiltin) { - loadBuiltinDictionary(); - dictionaryLoaded = (numberOfEntries() > skeletonCount); - if (!dictionaryLoaded) result = OFFalse; - } - if (loadExternal) { - if (loadExternalDictionaries()) - dictionaryLoaded = OFTrue; - else - result = OFFalse; - } - return result; -} - -OFBool -DcmDataDictionary::loadDictionary(const char* fileName, OFBool errorIfAbsent) -{ - - char lineBuf[DCM_MAXDICTLINESIZE + 1]; - FILE* f = NULL; - int lineNumber = 0; - char* lineFields[DCM_MAXDICTFIELDS + 1]; - int fieldsPresent; - DcmDictEntry* e; - int errorsEncountered = 0; - OFBool errorOnThisLine = OFFalse; - int i; - - DcmTagKey key, upperKey; - DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified; - DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified; - DcmVR vr; - char* vrName; - char* tagName; - char* privCreator; - int vmMin, vmMax = 1; - const char* standardVersion; - - /* first, check whether 'fileName' really points to a file (and not to a directory or the like) */ - if (!OFStandard::fileExists(fileName) || (f = fopen(fileName, "r")) == NULL) { - if (errorIfAbsent) { - DCMDATA_ERROR("DcmDataDictionary: Cannot open file: " << fileName); - } - return OFFalse; - } - - DCMDATA_DEBUG("DcmDataDictionary: Loading file: " << fileName); - - while (getLine(lineBuf, DCM_MAXDICTLINESIZE, f)) { - lineNumber++; - - if (onlyWhitespace(lineBuf)) { - continue; /* ignore this line */ - } - if (isaCommentLine(lineBuf)) { - continue; /* ignore this line */ - } - - errorOnThisLine = OFFalse; - - /* fields are tab separated */ - fieldsPresent = splitFields(lineBuf, lineFields, - DCM_MAXDICTFIELDS, - DCM_DICT_FIELD_SEPARATOR_CHAR); - - /* initialize dict entry fields */ - vrName = NULL; - tagName = NULL; - privCreator = NULL; - vmMin = vmMax = 1; - standardVersion = "DICOM"; - - switch (fieldsPresent) { - case 0: - case 1: - case 2: - DCMDATA_ERROR("DcmDataDictionary: "<< fileName << ": " - << "too few fields (line " << lineNumber << ")"); - errorOnThisLine = OFTrue; - break; - default: - DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " - << "too many fields (line " << lineNumber << "): "); - errorOnThisLine = OFTrue; - break; - case 5: - stripWhitespace(lineFields[4]); - standardVersion = lineFields[4]; - /* drop through to next case label */ - case 4: - /* the VM field is present */ - if (!parseVMField(lineFields[3], vmMin, vmMax)) { - DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " - << "bad VM field (line " << lineNumber << "): " << lineFields[3]); - errorOnThisLine = OFTrue; - } - /* drop through to next case label */ - case 3: - if (!parseWholeTagField(lineFields[0], key, upperKey, - groupRestriction, elementRestriction, privCreator)) - { - DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " - << "bad Tag field (line " << lineNumber << "): " << lineFields[0]); - errorOnThisLine = OFTrue; - } else { - /* all is OK */ - vrName = lineFields[1]; - stripWhitespace(vrName); - - tagName = lineFields[2]; - stripWhitespace(tagName); - } - } - - if (!errorOnThisLine) { - /* check the VR Field */ - vr.setVR(vrName); - if (vr.getEVR() == EVR_UNKNOWN) { - DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " - << "bad VR field (line " << lineNumber << "): " << vrName); - errorOnThisLine = OFTrue; - } - } - - if (!errorOnThisLine) { - e = new DcmDictEntry( - key.getGroup(), key.getElement(), - upperKey.getGroup(), upperKey.getElement(), - vr, tagName, vmMin, vmMax, standardVersion, OFTrue, - privCreator); - - e->setGroupRangeRestriction(groupRestriction); - e->setElementRangeRestriction(elementRestriction); - addEntry(e); - } - - for (i = 0; i < fieldsPresent; i++) { - free(lineFields[i]); - lineFields[i] = NULL; - } - - delete[] privCreator; - - if (errorOnThisLine) { - errorsEncountered++; - } - } - - fclose(f); - - /* return OFFalse in case of errors and set internal state accordingly */ - if (errorsEncountered == 0) { - dictionaryLoaded = OFTrue; - return OFTrue; - } - else { - dictionaryLoaded = OFFalse; - return OFFalse; - } -} - -#ifndef HAVE_GETENV - -static -char* getenv() { - return NULL; -} - -#endif /* !HAVE_GETENV */ - - - -OFBool -DcmDataDictionary::loadExternalDictionaries() -{ - const char* env = NULL; - size_t len; - int sepCnt = 0; - OFBool msgIfDictAbsent = OFTrue; - OFBool loadFailed = OFFalse; - - env = getenv(DCM_DICT_ENVIRONMENT_VARIABLE); - if ((env == NULL) || (strlen(env) == 0)) { - env = DCM_DICT_DEFAULT_PATH; - msgIfDictAbsent = OFFalse; - } - - if ((env != NULL) && (strlen(env) != 0)) { - len = strlen(env); - for (size_t i = 0; i < len; ++i) { - if (env[i] == ENVIRONMENT_PATH_SEPARATOR) { - sepCnt++; - } - } - - if (sepCnt == 0) { - if (!loadDictionary(env, msgIfDictAbsent)) { - return OFFalse; - } - } else { - char** dictArray; - - dictArray = OFstatic_cast(char **, malloc((sepCnt + 1) * sizeof(char*))); - - int ndicts = splitFields(env, dictArray, sepCnt + 1, - ENVIRONMENT_PATH_SEPARATOR); - - for (int ii = 0; ii < ndicts; ii++) { - if ((dictArray[ii] != NULL) && (strlen(dictArray[ii]) > 0)) { - if (!loadDictionary(dictArray[ii], msgIfDictAbsent)) { - loadFailed = OFTrue; - } - } - free(dictArray[ii]); - } - free(dictArray); - } - } - - return (loadFailed) ? (OFFalse) : (OFTrue); -} - - -void -DcmDataDictionary::addEntry(DcmDictEntry* e) -{ - if (e->isRepeating()) { - /* - * Find the best position in repeating tag list - * Existing entries are replaced if the ranges and repetition - * constraints are the same. - * If a range represents a subset of an existing range then it - * will be placed before it in the list. This ensures that a - * search will find the subset rather than the superset. - * Otherwise entries are appended to the end of the list. - */ - OFBool inserted = OFFalse; - - DcmDictEntryListIterator iter(repDict.begin()); - DcmDictEntryListIterator last(repDict.end()); - for (; !inserted && iter != last; ++iter) { - if (e->setEQ(**iter)) { - /* replace the old entry with the new */ - DcmDictEntry *old = *iter; - *iter = e; -#ifdef PRINT_REPLACED_DICTIONARY_ENTRIES - DCMDATA_WARN("replacing " << *old); -#endif - delete old; - inserted = OFTrue; - } else if (e->subset(**iter)) { - /* e is a subset of the current list position, insert before */ - repDict.insert(iter, e); - inserted = OFTrue; - } - } - if (!inserted) { - /* insert at end */ - repDict.push_back(e); - inserted = OFTrue; - } - } else { - hashDict.put(e); - } -} - -void -DcmDataDictionary::deleteEntry(const DcmDictEntry& entry) -{ - DcmDictEntry* e = NULL; - e = OFconst_cast(DcmDictEntry *, findEntry(entry)); - if (e != NULL) { - if (e->isRepeating()) { - repDict.remove(e); - delete e; - } else { - hashDict.del(entry.getKey(), entry.getPrivateCreator()); - } - } -} - -const DcmDictEntry* -DcmDataDictionary::findEntry(const DcmDictEntry& entry) const -{ - const DcmDictEntry* e = NULL; - - if (entry.isRepeating()) { - OFBool found = OFFalse; - DcmDictEntryListConstIterator iter(repDict.begin()); - DcmDictEntryListConstIterator last(repDict.end()); - for (; !found && iter != last; ++iter) { - if (entry.setEQ(**iter)) { - found = OFTrue; - e = *iter; - } - } - } else { - e = hashDict.get(entry, entry.getPrivateCreator()); - } - return e; -} - -const DcmDictEntry* -DcmDataDictionary::findEntry(const DcmTagKey& key, const char *privCreator) const -{ - /* search first in the normal tags dictionary and if not found - * then search in the repeating tags list. - */ - const DcmDictEntry* e = NULL; - - e = hashDict.get(key, privCreator); - if (e == NULL) { - /* search in the repeating tags dictionary */ - OFBool found = OFFalse; - DcmDictEntryListConstIterator iter(repDict.begin()); - DcmDictEntryListConstIterator last(repDict.end()); - for (; !found && iter != last; ++iter) { - if ((*iter)->contains(key, privCreator)) { - found = OFTrue; - e = *iter; - } - } - } - return e; -} - -const DcmDictEntry* -DcmDataDictionary::findEntry(const char *name) const -{ - const DcmDictEntry* e = NULL; - const DcmDictEntry* ePrivate = NULL; - - /* search first in the normal tags dictionary and if not found - * then search in the repeating tags list. - */ - DcmHashDictIterator iter; - for (iter = hashDict.begin(); (e == NULL) && (iter != hashDict.end()); ++iter) { - if ((*iter)->contains(name)) { - e = *iter; - if (e->getGroup() % 2) - { - /* tag is a private tag - continue search to be sure to find non-private keys first */ - if (!ePrivate) ePrivate = e; - e = NULL; - } - } - } - - if (e == NULL) { - /* search in the repeating tags dictionary */ - OFBool found = OFFalse; - DcmDictEntryListConstIterator iter2(repDict.begin()); - DcmDictEntryListConstIterator last(repDict.end()); - for (; !found && iter2 != last; ++iter2) { - if ((*iter2)->contains(name)) { - found = OFTrue; - e = *iter2; - } - } - } - - if (e == NULL && ePrivate != NULL) { - /* no standard key found - use the first private key found */ - e = ePrivate; - } - - return e; -} - - -/* ================================================================== */ - - -GlobalDcmDataDictionary::GlobalDcmDataDictionary() - : dataDict(NULL) -#ifdef WITH_THREADS - , dataDictLock() -#endif -{ -} - -GlobalDcmDataDictionary::~GlobalDcmDataDictionary() -{ - /* No threads may be active any more, so no locking needed */ - delete dataDict; -} - -void GlobalDcmDataDictionary::createDataDict() -{ - /* Make sure only one thread tries to initialize the dictionary */ -#ifdef WITH_THREADS - dataDictLock.wrlock(); -#endif -#ifdef DONT_LOAD_EXTERNAL_DICTIONARIES - const OFBool loadExternal = OFFalse; -#else - const OFBool loadExternal = OFTrue; -#endif - /* Make sure no other thread managed to create the dictionary - * before we got our write lock. */ - if (!dataDict) - dataDict = new DcmDataDictionary(OFTrue /*loadBuiltin*/, loadExternal); -#ifdef WITH_THREADS - dataDictLock.unlock(); -#endif -} - -const DcmDataDictionary& GlobalDcmDataDictionary::rdlock() -{ -#ifdef WITH_THREADS - dataDictLock.rdlock(); -#endif - if (!dataDict) - { - /* dataDictLock must not be locked during createDataDict() */ -#ifdef WITH_THREADS - dataDictLock.unlock(); -#endif - createDataDict(); -#ifdef WITH_THREADS - dataDictLock.rdlock(); -#endif - } - return *dataDict; -} - -DcmDataDictionary& GlobalDcmDataDictionary::wrlock() -{ -#ifdef WITH_THREADS - dataDictLock.wrlock(); -#endif - if (!dataDict) - { - /* dataDictLock must not be locked during createDataDict() */ -#ifdef WITH_THREADS - dataDictLock.unlock(); -#endif - createDataDict(); -#ifdef WITH_THREADS - dataDictLock.wrlock(); -#endif - } - return *dataDict; -} - -void GlobalDcmDataDictionary::unlock() -{ -#ifdef WITH_THREADS - dataDictLock.unlock(); -#endif -} - -OFBool GlobalDcmDataDictionary::isDictionaryLoaded() -{ - OFBool result = rdlock().isDictionaryLoaded(); - unlock(); - return result; -} - -void GlobalDcmDataDictionary::clear() -{ - wrlock().clear(); - unlock(); -} - - - - -// Function by the Orthanc project to load a dictionary from a memory -// buffer, which is necessary in sandboxed environments. This is an -// adapted version of DcmDataDictionary::loadDictionary(). - - -#include <boost/noncopyable.hpp> - -struct OrthancLinesIterator; - -// This plain old C class is implemented in "../../Core/Toolbox.h" -OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content); - -bool OrthancLinesIterator_GetLine(std::string& target, - const OrthancLinesIterator* iterator); - -void OrthancLinesIterator_Next(OrthancLinesIterator* iterator); - -void OrthancLinesIterator_Free(OrthancLinesIterator* iterator); - - -class LinesIterator : public boost::noncopyable -{ -private: - OrthancLinesIterator* iterator_; - -public: - LinesIterator(const std::string& content) : - iterator_(NULL) - { - iterator_ = OrthancLinesIterator_Create(content); - } - - ~LinesIterator() - { - if (iterator_ != NULL) - { - OrthancLinesIterator_Free(iterator_); - iterator_ = NULL; - } - } - - bool GetLine(std::string& target) const - { - if (iterator_ != NULL) - { - return OrthancLinesIterator_GetLine(target, iterator_); - } - else - { - return false; - } - } - - void Next() - { - if (iterator_ != NULL) - { - OrthancLinesIterator_Next(iterator_); - } - } -}; - - - -OFBool -DcmDataDictionary::loadFromMemory(const std::string& content, OFBool errorIfAbsent) -{ - int lineNumber = 0; - char* lineFields[DCM_MAXDICTFIELDS + 1]; - int fieldsPresent; - DcmDictEntry* e; - int errorsEncountered = 0; - OFBool errorOnThisLine = OFFalse; - int i; - - DcmTagKey key, upperKey; - DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified; - DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified; - DcmVR vr; - char* vrName; - char* tagName; - char* privCreator; - int vmMin, vmMax = 1; - const char* standardVersion; - - LinesIterator iterator(content); - - std::string line; - while (iterator.GetLine(line)) { - iterator.Next(); - - if (line.size() >= DCM_MAXDICTLINESIZE) { - DCMDATA_ERROR("DcmDataDictionary: Too long line: " << line); - continue; - } - - lineNumber++; - - if (onlyWhitespace(line.c_str())) { - continue; /* ignore this line */ - } - if (isaCommentLine(line.c_str())) { - continue; /* ignore this line */ - } - - errorOnThisLine = OFFalse; - - /* fields are tab separated */ - fieldsPresent = splitFields(line.c_str(), lineFields, - DCM_MAXDICTFIELDS, - DCM_DICT_FIELD_SEPARATOR_CHAR); - - /* initialize dict entry fields */ - vrName = NULL; - tagName = NULL; - privCreator = NULL; - vmMin = vmMax = 1; - standardVersion = "DICOM"; - - switch (fieldsPresent) { - case 0: - case 1: - case 2: - DCMDATA_ERROR("DcmDataDictionary: " - << "too few fields (line " << lineNumber << ")"); - errorOnThisLine = OFTrue; - break; - default: - DCMDATA_ERROR("DcmDataDictionary: " - << "too many fields (line " << lineNumber << "): "); - errorOnThisLine = OFTrue; - break; - case 5: - stripWhitespace(lineFields[4]); - standardVersion = lineFields[4]; - /* drop through to next case label */ - case 4: - /* the VM field is present */ - if (!parseVMField(lineFields[3], vmMin, vmMax)) { - DCMDATA_ERROR("DcmDataDictionary: " - << "bad VM field (line " << lineNumber << "): " << lineFields[3]); - errorOnThisLine = OFTrue; - } - /* drop through to next case label */ - case 3: - if (!parseWholeTagField(lineFields[0], key, upperKey, - groupRestriction, elementRestriction, privCreator)) - { - DCMDATA_ERROR("DcmDataDictionary: " - << "bad Tag field (line " << lineNumber << "): " << lineFields[0]); - errorOnThisLine = OFTrue; - } else { - /* all is OK */ - vrName = lineFields[1]; - stripWhitespace(vrName); - - tagName = lineFields[2]; - stripWhitespace(tagName); - } - } - - if (!errorOnThisLine) { - /* check the VR Field */ - vr.setVR(vrName); - if (vr.getEVR() == EVR_UNKNOWN) { - DCMDATA_ERROR("DcmDataDictionary: " - << "bad VR field (line " << lineNumber << "): " << vrName); - errorOnThisLine = OFTrue; - } - } - - if (!errorOnThisLine) { - e = new DcmDictEntry( - key.getGroup(), key.getElement(), - upperKey.getGroup(), upperKey.getElement(), - vr, tagName, vmMin, vmMax, standardVersion, OFTrue, - privCreator); - - e->setGroupRangeRestriction(groupRestriction); - e->setElementRangeRestriction(elementRestriction); - addEntry(e); - } - - for (i = 0; i < fieldsPresent; i++) { - free(lineFields[i]); - lineFields[i] = NULL; - } - - delete[] privCreator; - - if (errorOnThisLine) { - errorsEncountered++; - } - } - - /* return OFFalse in case of errors and set internal state accordingly */ - if (errorsEncountered == 0) { - dictionaryLoaded = OFTrue; - return OFTrue; - } - else { - dictionaryLoaded = OFFalse; - return OFFalse; - } -}
--- a/Resources/WebAssembly/dcdict.h Wed Mar 18 08:59:06 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,302 +0,0 @@ -/* - * - * Copyright (C) 1994-2015, OFFIS e.V. - * All rights reserved. See COPYRIGHT file for details. - * - * This software and supporting documentation were developed by - * - * OFFIS e.V. - * R&D Division Health - * Escherweg 2 - * D-26121 Oldenburg, Germany - * - * - * Module: dcmdata - * - * Author: Andrew Hewett - * - * Purpose: Interface for loadable DICOM data dictionary - * - */ - - -#ifndef DCMDICT_H -#define DCMDICT_H - -#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ - -#include "dcmtk/ofstd/ofthread.h" -#include "dcmtk/dcmdata/dchashdi.h" - -/// maximum length of a line in the loadable DICOM dictionary -#define DCM_MAXDICTLINESIZE 2048 - -/// maximum number of fields per entry in the loadable DICOM dictionary -#define DCM_MAXDICTFIELDS 6 - -/// environment variable pointing to the data dictionary file -#define DCM_DICT_ENVIRONMENT_VARIABLE "DCMDICTPATH" - -#ifndef DCM_DICT_DEFAULT_PATH -/* -** The default dictionary path is system dependent. It should -** be defined in a configuration file included from "osconfig.h" -*/ -#error "DCM_DICT_DEFAULT_PATH is not defined via osconfig.h" -#endif /* !DCM_DICT_DEFAULT_PATH */ - -#ifndef ENVIRONMENT_PATH_SEPARATOR -#define ENVIRONMENT_PATH_SEPARATOR '\n' /* at least define something unlikely */ -#endif - - -/** this class implements a loadable DICOM Data Dictionary - */ -class DCMTK_DCMDATA_EXPORT DcmDataDictionary -{ -public: - - /** constructor - * @param loadBuiltin flag indicating if a built-in data dictionary - * (if any) should be loaded. - * @param loadExternal flag indicating if an external data dictionary - * should be read from file. - */ - DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal); - - /// destructor - ~DcmDataDictionary(); - - /** checks if a data dictionary is loaded (excluding the skeleton dictionary) - * @return true if loaded, false if no dictionary is present - */ - OFBool isDictionaryLoaded() const { return dictionaryLoaded; } - - /// returns the number of normal (non-repeating) tag entries - int numberOfNormalTagEntries() const { return hashDict.size(); } - - /// returns the number of repeating tag entries - int numberOfRepeatingTagEntries() const { return OFstatic_cast(int, repDict.size()); } - - /** returns the number of dictionary entries that were loaded - * either from file or from a built-in dictionary or both. - */ - int numberOfEntries() const - { return numberOfNormalTagEntries() - + numberOfRepeatingTagEntries() - skeletonCount; } - - /** returns the number of skeleton entries. The skeleton is a collection - * of dictionary entries which are always present, even if neither internal - * nor external dictionary have been loaded. It contains very basic - * things like item delimitation and sequence delimitation. - */ - int numberOfSkeletonEntries() const { return skeletonCount; } - - /** reload data dictionaries. First, all dictionary entries are deleted. - * @param loadBuiltin flag indicating if a built-in data dictionary - * (if any) should be loaded. - * @param loadExternal flag indicating if an external data dictionary - * should be read from file. - * @return true if reload was successful, false if an error occurred - */ - OFBool reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal); - - /** load a particular dictionary from file. - * @param fileName filename - * @param errorIfAbsent causes the method to return false - * if the file cannot be opened - * @return false if the file contains a parse error or if the file could - * not be opened and errorIfAbsent was set, true otherwise. - */ - OFBool loadDictionary(const char* fileName, OFBool errorIfAbsent = OFTrue); - - /** dictionary lookup for the given tag key and private creator name. - * First the normal tag dictionary is searched. If not found - * then the repeating tag dictionary is searched. - * @param key tag key - * @param privCreator private creator name, may be NULL - */ - const DcmDictEntry* findEntry(const DcmTagKey& key, const char *privCreator) const; - - /** dictionary lookup for the given attribute name. - * First the normal tag dictionary is searched. If not found - * then the repeating tag dictionary is searched. - * Only considers standard attributes (i. e. without private creator) - * @param name attribute name - */ - const DcmDictEntry* findEntry(const char *name) const; - - /// deletes all dictionary entries - void clear(); - - /** adds an entry to the dictionary. Must be allocated via new. - * The entry becomes the property of the dictionary and will be - * deallocated (via delete) upon clear() or dictionary destruction. - * If an equivalent entry already exists it will be replaced by - * the new entry and the old entry deallocated (via delete). - * @param entry pointer to new entry - */ - void addEntry(DcmDictEntry* entry); - - /* Iterators to access the normal and the repeating entries */ - - /// returns an iterator to the start of the normal (non-repeating) dictionary - DcmHashDictIterator normalBegin() { return hashDict.begin(); } - - /// returns an iterator to the end of the normal (non-repeating) dictionary - DcmHashDictIterator normalEnd() { return hashDict.end(); } - - /// returns an iterator to the start of the repeating tag dictionary - DcmDictEntryListIterator repeatingBegin() { return repDict.begin(); } - - /// returns an iterator to the end of the repeating tag dictionary - DcmDictEntryListIterator repeatingEnd() { return repDict.end(); } - - // Function by the Orthanc project to load a dictionary from a - // memory buffer, which is necessary in sandboxed - // environments. This is an adapted version of - // DcmDataDictionary::loadDictionary(). - OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue); - -private: - - /** private undefined assignment operator - */ - DcmDataDictionary &operator=(const DcmDataDictionary &); - - /** private undefined copy constructor - */ - DcmDataDictionary(const DcmDataDictionary &); - - /** loads external dictionaries defined via environment variables - * @return true if successful - */ - OFBool loadExternalDictionaries(); - - /** loads a builtin (compiled) data dictionary. - * Depending on which code is in use, this function may not - * do anything. - */ - void loadBuiltinDictionary(); - - /** loads the skeleton dictionary (the bare minimum needed to run) - * @return true if successful - */ - OFBool loadSkeletonDictionary(); - - /** looks up the given directory entry in the two dictionaries. - * @return pointer to entry if found, NULL otherwise - */ - const DcmDictEntry* findEntry(const DcmDictEntry& entry) const; - - /** deletes the given entry from either dictionary - */ - void deleteEntry(const DcmDictEntry& entry); - - - /** dictionary of normal tags - */ - DcmHashDict hashDict; - - /** dictionary of repeating tags - */ - DcmDictEntryList repDict; - - /** the number of skeleton entries - */ - int skeletonCount; - - /** is a dictionary loaded (more than skeleton) - */ - OFBool dictionaryLoaded; - -}; - - -/** global singleton dicom dictionary that is used by DCMTK in order to lookup - * attribute VR, tag names and so on. The dictionary is internally populated - * on first use, if the user accesses it via rdlock() or wrlock(). The - * dictionary allows safe read (shared) and write (exclusive) access from - * multiple threads in parallel. - */ -class DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary -{ -public: - /** constructor. - */ - GlobalDcmDataDictionary(); - - /** destructor - */ - ~GlobalDcmDataDictionary(); - - /** acquires a read lock and returns a const reference to - * the dictionary. - * @return const reference to dictionary - */ - const DcmDataDictionary& rdlock(); - - /** acquires a write lock and returns a non-const reference - * to the dictionary. - * @return non-const reference to dictionary. - */ - DcmDataDictionary& wrlock(); - - /** unlocks the read or write lock which must have been acquired previously. - */ - void unlock(); - - /** checks if a data dictionary has been loaded. This method acquires and - * releases a read lock. It must not be called with another lock on the - * dictionary being held by the calling thread. - * @return OFTrue if dictionary has been loaded, OFFalse otherwise. - */ - OFBool isDictionaryLoaded(); - - /** erases the contents of the dictionary. This method acquires and - * releases a write lock. It must not be called with another lock on the - * dictionary being held by the calling thread. This method is intended - * as a help for debugging memory leaks. - */ - void clear(); - -private: - /** private undefined assignment operator - */ - GlobalDcmDataDictionary &operator=(const GlobalDcmDataDictionary &); - - /** private undefined copy constructor - */ - GlobalDcmDataDictionary(const GlobalDcmDataDictionary &); - - /** create the data dictionary instance for this class. Used for first - * intialization. The caller must not have dataDictLock locked. - */ - void createDataDict(); - - /** the data dictionary managed by this class - */ - DcmDataDictionary *dataDict; - -#ifdef WITH_THREADS - /** the read/write lock used to protect access from multiple threads - */ - OFReadWriteLock dataDictLock; -#endif -}; - - -/** The Global DICOM Data Dictionary. - * Will be created before main() starts and gets populated on its first use. - * Tries to load a builtin data dictionary (if compiled in). - * Tries to load data dictionaries from files specified by - * the DCMDICTPATH environment variable. If this environment - * variable does not exist then a default file is loaded (if - * it exists). - * It is possible that no data dictionary gets loaded. This - * is likely to cause unexpected behaviour in the dcmdata - * toolkit classes. - */ -extern DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary dcmDataDict; - -#endif
--- a/Resources/WindowsResources.py Wed Mar 18 08:59:06 2020 +0100 +++ b/Resources/WindowsResources.py Thu Mar 19 11:48:30 2020 +0100 @@ -3,7 +3,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2019 Osimis S.A., Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/TODO Wed Mar 18 08:59:06 2020 +0100 +++ b/TODO Thu Mar 19 11:48:30 2020 +0100 @@ -114,7 +114,7 @@ * Image transcoding API * Add plugins for normalized operations (notably so as to support Print SCU/SCP): - https://www.medicalconnections.co.uk/kb/DICOM_Print_Service + https://web.archive.org/web/20170923150432/https://www.medicalconnections.co.uk/kb/DICOM_Print_Service * Provide access to the Orthanc::DicomUserConnection class in plugins: https://groups.google.com/d/msg/orthanc-users/ycDA1xPuTRY/nsT2_GOtEgAJ * Provide a C++ callback similar to "ReceivedInstanceFilter()" in Lua @@ -193,3 +193,31 @@ * Create REST bindings with Slicer * Create REST bindings with Horos/OsiriX + + +==== +Misc +==== + +------- +Logging +------- + +This is a wish expressed in issue #65 on BitBucket: + +"Different levels for various modules (nice to have) + +We often need to debug DICOM interactions and logs are 'polluted' by +logs from the Rest API, i.e: since I have a process calling the +/changes route every 5 second, I'm lost in 17000 /changes record in my +logs everyday while I just want to check what kind of C-Find requests +are issued by a modality. If we go for it, logs could be configured +via the configuration file: i.e.: + +{ + "Web": "info", + "Dicom": "debug", + "Db": "warning", + "WebViewer": "warning", // here WebViewer is a plugin + "Generic": "warning", // for all stuffs we could not port to the new logging API or old plugins .... +}"
--- a/UnitTestsSources/DatabaseLookupTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/DatabaseLookupTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/DicomMapTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/DicomMapTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/Compatibility.h" #include "../Core/OrthancException.h" #include "../Core/DicomFormat/DicomMap.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" @@ -121,7 +122,7 @@ ASSERT_EQ(1u, s.size()); ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin()); - std::auto_ptr<DicomMap> mm(m.Clone()); + std::unique_ptr<DicomMap> mm(m.Clone()); ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).GetContent()); m.SetValue(DICOM_TAG_PATIENT_ID, "Hello", false); @@ -450,10 +451,10 @@ dataset.insertEmptyElement(DCM_StudyID, OFFalse); { - std::auto_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence)); + std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence)); { - std::auto_ptr<DcmItem> item(new DcmItem); + std::unique_ptr<DcmItem> item(new DcmItem); item->putAndInsertString(DCM_ReferencedSOPInstanceUID, "nope", OFFalse); ASSERT_TRUE(sequence->insert(item.release(), false, false).good()); } @@ -566,6 +567,24 @@ } +TEST(DicomMap, RemoveBinary) +{ + DicomMap b; + b.SetValue(DICOM_TAG_PATIENT_NAME, "A", false); + b.SetValue(DICOM_TAG_PATIENT_ID, "B", true); + b.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, DicomValue()); // NULL + b.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, DicomValue("C", false)); + b.SetValue(DICOM_TAG_SOP_INSTANCE_UID, DicomValue("D", true)); + + b.RemoveBinaryTags(); + + std::string s; + ASSERT_EQ(2u, b.GetSize()); + ASSERT_TRUE(b.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false)); ASSERT_EQ("A", s); + ASSERT_TRUE(b.LookupStringValue(s, DICOM_TAG_SERIES_INSTANCE_UID, false)); ASSERT_EQ("C", s); +} + + TEST(DicomWebJson, Multiplicity) { @@ -576,7 +595,7 @@ dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1\\2.3\\4"); dicom.ReplacePlainString(DICOM_TAG_IMAGE_POSITION_PATIENT, ""); - Orthanc::DicomWebJsonVisitor visitor; + DicomWebJsonVisitor visitor; dicom.Apply(visitor); { @@ -615,7 +634,7 @@ ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)); std::vector<std::string> v; - Orthanc::Toolbox::TokenizeString(v, s, '\\'); + Toolbox::TokenizeString(v, s, '\\'); ASSERT_FLOAT_EQ(1.0f, boost::lexical_cast<float>(v[0])); ASSERT_FLOAT_EQ(2.3f, boost::lexical_cast<float>(v[1])); ASSERT_FLOAT_EQ(4.0f, boost::lexical_cast<float>(v[2])); @@ -630,7 +649,7 @@ ParsedDicomFile dicom(false); dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1.5\\\\\\2.5"); - Orthanc::DicomWebJsonVisitor visitor; + DicomWebJsonVisitor visitor; dicom.Apply(visitor); { @@ -660,7 +679,7 @@ ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)); std::vector<std::string> v; - Orthanc::Toolbox::TokenizeString(v, s, '\\'); + Toolbox::TokenizeString(v, s, '\\'); ASSERT_FLOAT_EQ(1.5f, boost::lexical_cast<float>(v[0])); ASSERT_TRUE(v[1].empty()); ASSERT_TRUE(v[2].empty()); @@ -677,7 +696,7 @@ // "dicom.GetDcmtkObject().getDataset()->putAndInsertTagKey(tag, // value)" that was not available in DCMTK 3.6.0 - std::auto_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag))); + std::unique_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag))); DcmTagKey v = ToDcmtkBridge::Convert(value); if (!element->putTagVal(v).good()) @@ -707,9 +726,9 @@ dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO"); dicom.ReplacePlainString(DicomTag(0x0010, 0x4000), "LT"); dicom.ReplacePlainString(DicomTag(0x0028, 0x2000), "OB"); - dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "OD"); - dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "OF"); - dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46"); + dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "3.14159"); // OD (other double) + dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "2.71828"); // OF (other float) + dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46"); // OL (other long) ASSERT_THROW(dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "O"), OrthancException); dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "OWOW"); dicom.ReplacePlainString(DicomTag(0x0010, 0x0010), "PN"); @@ -726,7 +745,7 @@ dicom.ReplacePlainString(DicomTag(0x0008, 0x0301), "17"); // US dicom.ReplacePlainString(DicomTag(0x0040, 0x0031), "UT"); - Orthanc::DicomWebJsonVisitor visitor; + DicomWebJsonVisitor visitor; dicom.Apply(visitor); std::string s; @@ -766,16 +785,17 @@ #if DCMTK_VERSION_NUMBER >= 361 ASSERT_EQ("OD", visitor.GetResult() ["7FE00009"]["vr"].asString()); + ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(visitor.GetResult() ["7FE00009"]["Value"][0].asString())); #else ASSERT_EQ("UN", visitor.GetResult() ["7FE00009"]["vr"].asString()); + Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString()); + ASSERT_EQ(8u, s.size()); // Because of padding + ASSERT_EQ(0, s[7]); + ASSERT_EQ("3.14159", s.substr(0, 7)); #endif - Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString()); - ASSERT_EQ("OD", s); - ASSERT_EQ("OF", visitor.GetResult() ["00640009"]["vr"].asString()); - Toolbox::DecodeBase64(s, visitor.GetResult() ["00640009"]["InlineBinary"].asString()); - ASSERT_EQ("OF", s); + ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(visitor.GetResult() ["00640009"]["Value"][0].asString())); #if DCMTK_VERSION_NUMBER < 361 ASSERT_EQ("UN", visitor.GetResult() ["00660040"]["vr"].asString()); @@ -784,10 +804,9 @@ #elif DCMTK_VERSION_NUMBER == 361 ASSERT_EQ("UL", visitor.GetResult() ["00660040"]["vr"].asString()); ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt()); -#elif DCMTK_VERSION_NUMBER > 361 +#else ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString()); - Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString()); - ASSERT_EQ("46", s); + ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt()); #endif ASSERT_EQ("OW", visitor.GetResult() ["00281201"]["vr"].asString()); @@ -875,8 +894,18 @@ ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0070), false)); ASSERT_EQ("LO", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x4000), false)); ASSERT_EQ("LT", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x2000), true)); ASSERT_EQ("OB", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true)); ASSERT_EQ("OD", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true)); ASSERT_EQ("OF", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true)); + +#if DCMTK_VERSION_NUMBER >= 361 + ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(s)); +#else + ASSERT_EQ(8u, s.size()); // Because of padding + ASSERT_EQ(0, s[7]); + ASSERT_EQ("3.14159", s.substr(0, 7)); +#endif + + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true)); + ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(s)); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x1201), true)); ASSERT_EQ("OWOW", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x0010), false)); ASSERT_EQ("PN", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0050), false)); ASSERT_EQ("SH", s); @@ -884,20 +913,23 @@ ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x9219), false)); ASSERT_EQ("-16", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0081), false)); ASSERT_EQ("ST", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0013), false)); ASSERT_EQ("TM", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false)); ASSERT_EQ("UC", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0016), false)); ASSERT_EQ("UI", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1161), false)); ASSERT_EQ("128", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x4342, 0x1234), true)); ASSERT_EQ("UN", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false)); ASSERT_EQ("UR", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false)); ASSERT_EQ("17", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0031), false)); ASSERT_EQ("UT", s); -#if DCMTK_VERSION_NUMBER == 361 - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false)); +#if DCMTK_VERSION_NUMBER >= 361 + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false)); ASSERT_EQ("46", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false)); ASSERT_EQ("UC", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false)); ASSERT_EQ("UR", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false)); ASSERT_EQ("17", s); #else - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true)); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true)); ASSERT_EQ("46", s); // OL + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), true)); ASSERT_EQ("UC", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), true)); ASSERT_EQ("UR", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), true)); ASSERT_EQ("17", s); // US (but tag unknown to DCMTK 3.6.0) #endif - ASSERT_EQ("46", s); + } } @@ -907,11 +939,11 @@ ParsedDicomFile dicom(false); { - std::auto_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence)); + std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence)); for (unsigned int i = 0; i < 3; i++) { - std::auto_ptr<DcmItem> item(new DcmItem); + std::unique_ptr<DcmItem> item(new DcmItem); std::string s = "item" + boost::lexical_cast<std::string>(i); item->putAndInsertString(DCM_ReferencedSOPInstanceUID, s.c_str(), OFFalse); ASSERT_TRUE(sequence->insert(item.release(), false, false).good()); @@ -920,7 +952,7 @@ ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->insert(sequence.release(), false, false).good()); } - Orthanc::DicomWebJsonVisitor visitor; + DicomWebJsonVisitor visitor; dicom.Apply(visitor); ASSERT_EQ("SQ", visitor.GetResult() ["00081115"]["vr"].asString()); @@ -947,6 +979,70 @@ { DicomMap m; m.FromDicomWeb(visitor.GetResult()); - ASSERT_EQ(0u, m.GetSize()); // Sequences are not handled by Orthanc::DicomMap + ASSERT_EQ(0u, m.GetSize()); // Sequences are not handled by DicomMap } } + + +TEST(DicomWebJson, PixelSpacing) +{ + // Test related to locales: Make sure that decimal separator is + // correctly handled (dot "." vs comma ",") + ParsedDicomFile source(false); + source.ReplacePlainString(DICOM_TAG_PIXEL_SPACING, "1.5\\1.3"); + + DicomWebJsonVisitor visitor; + source.Apply(visitor); + + DicomMap target; + target.FromDicomWeb(visitor.GetResult()); + + ASSERT_EQ("DS", visitor.GetResult() ["00280030"]["vr"].asString()); + ASSERT_FLOAT_EQ(1.5f, visitor.GetResult() ["00280030"]["Value"][0].asFloat()); + ASSERT_FLOAT_EQ(1.3f, visitor.GetResult() ["00280030"]["Value"][1].asFloat()); + + std::string s; + ASSERT_TRUE(target.LookupStringValue(s, DICOM_TAG_PIXEL_SPACING, false)); + ASSERT_EQ(s, "1.5\\1.3"); +} + + +TEST(DicomMap, MainTagNames) +{ + ASSERT_EQ(3, ResourceType_Instance - ResourceType_Patient); + + for (int i = ResourceType_Patient; i <= ResourceType_Instance; i++) + { + ResourceType level = static_cast<ResourceType>(i); + + std::set<DicomTag> tags; + DicomMap::GetMainDicomTags(tags, level); + + for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + DicomMap a; + a.SetValue(*it, "TEST", false); + + Json::Value json; + a.DumpMainDicomTags(json, level); + + ASSERT_EQ(Json::objectValue, json.type()); + ASSERT_EQ(1u, json.getMemberNames().size()); + + std::string name = json.getMemberNames() [0]; + EXPECT_EQ(name, FromDcmtkBridge::GetTagName(*it, "")); + + DicomMap b; + b.ParseMainDicomTags(json, level); + + ASSERT_EQ(1u, b.GetSize()); + ASSERT_EQ("TEST", b.GetStringValue(*it, "", false)); + + std::string main = it->GetMainTagsName(); + if (!main.empty()) + { + ASSERT_EQ(main, name); + } + } + } +}
--- a/UnitTestsSources/FileStorageTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/FileStorageTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/FromDcmtkTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/Compatibility.h" #include "../Core/DicomNetworking/DicomFindAnswers.h" #include "../Core/DicomParsing/DicomModification.h" #include "../Core/DicomParsing/DicomWebJsonVisitor.h" @@ -95,7 +96,7 @@ { char b[1024]; sprintf(b, "UnitTestsResults/anon%06d.dcm", i); - std::auto_ptr<ParsedDicomFile> f(o.Clone(false)); + std::unique_ptr<ParsedDicomFile> f(o.Clone(false)); if (i > 4) o.ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); m.Apply(*f); @@ -108,7 +109,7 @@ { ASSERT_EQ(DICOM_TAG_PATIENT_NAME, FromDcmtkBridge::ParseTag("PatientName")); - const DicomTag privateTag(0x0045, 0x0010); + const DicomTag privateTag(0x0045, 0x1010); const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020")); ASSERT_TRUE(privateTag.IsPrivate()); ASSERT_TRUE(privateTag2.IsPrivate()); @@ -119,19 +120,19 @@ ParsedDicomFile o(true); o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou"); ASSERT_FALSE(o.GetTagValue(s, privateTag)); - o.Insert(privateTag, "private tag", false); + o.Insert(privateTag, "private tag", false, "OrthancCreator"); ASSERT_TRUE(o.GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent), OrthancException); + ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent, "OrthancCreator"), OrthancException); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent); + o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent, "OrthancCreator"); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent); + o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); ASSERT_TRUE(o.GetTagValue(s, privateTag2)); ASSERT_STREQ("hello", s.c_str()); - o.ReplacePlainString(privateTag2, "hello world"); + o.Replace(privateTag2, std::string("hello world"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); ASSERT_TRUE(o.GetTagValue(s, privateTag2)); ASSERT_STREQ("hello world", s.c_str()); @@ -290,7 +291,7 @@ f.SetEncoding(testEncodings[i]); std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false); - f.Insert(DICOM_TAG_PATIENT_NAME, s, false); + f.Insert(DICOM_TAG_PATIENT_NAME, s, false, ""); f.SaveToMemoryBuffer(dicom); } @@ -402,12 +403,12 @@ // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#private-class-members TEST(FromDcmtkBridge, FromJson) { - std::auto_ptr<DcmElement> element; + std::unique_ptr<DcmElement> element; { Json::Value a; a = "Hello"; - element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)); + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8, "")); Json::Value b; std::set<DicomTag> ignoreTagLength; @@ -439,20 +440,20 @@ Json::Value a; a = "Hello"; // Cannot assign a string to a sequence - ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false, Encoding_Utf8)), OrthancException); + ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false, Encoding_Utf8, "")), OrthancException); } { Json::Value a = Json::arrayValue; a.append("Hello"); // Cannot assign an array to a string - ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)), OrthancException); + ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8, "")), OrthancException); } { Json::Value a; a = "data:application/octet-stream;base64,SGVsbG8="; // echo -n "Hello" | base64 - element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8)); + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8, "")); Json::Value b; std::set<DicomTag> ignoreTagLength; @@ -464,7 +465,7 @@ { Json::Value a = Json::arrayValue; CreateSampleJson(a); - element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8)); + element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8, "")); { Json::Value b; @@ -506,8 +507,8 @@ { ParsedDicomFile f(true); - f.Insert(DICOM_TAG_PATIENT_NAME, "World", false); - ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException); // Already existing tag + f.Insert(DICOM_TAG_PATIENT_NAME, "World", false, ""); + ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false, ""), OrthancException); // Already existing tag f.ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, "Toto"); // (*) f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "Tata"); // (**) @@ -515,16 +516,16 @@ ASSERT_FALSE(f.LookupTransferSyntax(s)); ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), - false, DicomReplaceMode_ThrowIfAbsent), OrthancException); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent); + false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent, ""); ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent, ""); ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); ASSERT_EQ(s, "Accession"); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent, ""); ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); ASSERT_EQ(s, "Accession2"); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent, ""); ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); ASSERT_EQ(s, "Accession3"); @@ -552,20 +553,20 @@ ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); f.Remove(REFERENCED_STUDY_SEQUENCE); // No effect - f.Insert(REFERENCED_STUDY_SEQUENCE, a, true); + f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, ""); ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); - ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true), OrthancException); + ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, ""), OrthancException); f.Remove(REFERENCED_STUDY_SEQUENCE); ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); - f.Insert(REFERENCED_STUDY_SEQUENCE, a, true); + f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, ""); ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); - ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent), OrthancException); + ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException); ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); - f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent); + f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent, ""); ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); - f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent); + f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent, ""); ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); { @@ -580,8 +581,8 @@ } a = "data:application/octet-stream;base64,VGF0YQ=="; // echo -n "Tata" | base64 - f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent); // (*) - f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent); // (**) + f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent, ""); // (*) + f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent, ""); // (**) std::string s; ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID)); @@ -614,7 +615,7 @@ } Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false); - f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent); + f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent, ""); Json::Value v; f.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); @@ -626,13 +627,13 @@ TEST(ParsedDicomFile, ToJsonFlags1) { - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_PersonName, "MyPrivateTag", 1, 1, ""); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1, "OrthancCreator"); FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, ""); ParsedDicomFile f(true); - f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false); // Even group => public tag - f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false); // Even group => public, unknown tag - f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false); // Odd group => private tag + f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false, ""); // Even group => public tag + f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false, ""); // Even group => public, unknown tag + f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false, "OrthancCreator"); // Odd group => private tag Json::Value v; f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); @@ -644,7 +645,7 @@ ASSERT_EQ(Json::stringValue, v["7050,1000"].type()); ASSERT_EQ("Some public tag", v["7050,1000"].asString()); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); @@ -653,7 +654,14 @@ ASSERT_EQ("Some public tag", v["7050,1000"].asString()); ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); - f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags), 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(6u, v.getMemberNames().size()); + ASSERT_FALSE(v.isMember("7052,1000")); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_FALSE(v.isMember("7053,1000")); + + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); @@ -666,7 +674,7 @@ ASSERT_EQ("application/octet-stream", mime); ASSERT_EQ("Some private tag", content); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -675,7 +683,7 @@ ASSERT_EQ("Some public tag", v["7050,1000"].asString()); ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -687,7 +695,7 @@ ASSERT_EQ("application/octet-stream", mime); ASSERT_EQ("Some unknown tag", content); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(8u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -702,7 +710,7 @@ TEST(ParsedDicomFile, ToJsonFlags2) { ParsedDicomFile f(true); - f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false); + f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false, ""); Json::Value v; f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); @@ -810,8 +818,8 @@ { - std::auto_ptr<ParsedDicomFile> dicom - (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers))); + std::unique_ptr<ParsedDicomFile> dicom + (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), "")); Json::Value vv; dicom->DatasetToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0); @@ -826,8 +834,8 @@ { - std::auto_ptr<ParsedDicomFile> dicom - (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers))); + std::unique_ptr<ParsedDicomFile> dicom + (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), "")); Json::Value vv; dicom->DatasetToJson(vv, DicomToJsonFormat_Human, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData), 0); @@ -840,8 +848,8 @@ { - std::auto_ptr<ParsedDicomFile> dicom - (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme))); + std::unique_ptr<ParsedDicomFile> dicom + (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme), "")); Json::Value vv; dicom->DatasetToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0); @@ -909,7 +917,7 @@ Orthanc::SystemToolbox::ReadFile(s, PATH); Orthanc::ParsedDicomFile f(s); - std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); + std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); ASSERT_EQ(256u, decoded->GetWidth()); ASSERT_EQ(256u, decoded->GetHeight()); ASSERT_EQ(Orthanc::PixelFormat_Grayscale8, decoded->GetFormat()); @@ -971,7 +979,7 @@ Orthanc::SystemToolbox::ReadFile(s, PATH); Orthanc::ParsedDicomFile f(s); - std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); + std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); ASSERT_EQ(384u, decoded->GetWidth()); ASSERT_EQ(256u, decoded->GetHeight()); ASSERT_EQ(Orthanc::PixelFormat_RGB24, decoded->GetFormat()); @@ -1028,7 +1036,7 @@ Orthanc::SystemToolbox::ReadFile(s, PATH); Orthanc::ParsedDicomFile f(s); - std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); + std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); ASSERT_EQ(256u, decoded->GetWidth()); ASSERT_EQ(256u, decoded->GetHeight()); ASSERT_EQ(Orthanc::PixelFormat_Grayscale16, decoded->GetFormat()); @@ -1084,7 +1092,7 @@ Orthanc::SystemToolbox::ReadFile(s, PATH); Orthanc::ParsedDicomFile f(s); - std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); + std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0)); ASSERT_EQ(256u, decoded->GetWidth()); ASSERT_EQ(256u, decoded->GetHeight()); ASSERT_EQ(Orthanc::PixelFormat_SignedGrayscale16, decoded->GetFormat()); @@ -1904,3 +1912,355 @@ ASSERT_TRUE(lines[3].empty()); } + + + +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + +#include "../Core/DicomParsing/Internals/DicomFrameIndex.h" + +#include <dcmtk/dcmdata/dcostrmb.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcpxitem.h> + + +namespace Orthanc +{ + class IDicomTranscoder : public boost::noncopyable + { + public: + virtual ~IDicomTranscoder() + { + } + + virtual DicomTransferSyntax GetTransferSyntax() = 0; + + virtual std::string GetSopClassUid() = 0; + + virtual std::string GetSopInstanceUid() = 0; + + virtual unsigned int GetFramesCount() = 0; + + virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0; + + virtual void GetCompressedFrame(std::string& target, + unsigned int frame) = 0; + + virtual IDicomTranscoder* Transcode(std::set<DicomTransferSyntax> syntaxes, + bool allowNewSopInstanceUid) = 0; + }; + + + class DcmtkTranscoder : public IDicomTranscoder + { + private: + std::unique_ptr<DcmFileFormat> dicom_; + std::unique_ptr<DicomFrameIndex> index_; + DicomTransferSyntax transferSyntax_; + std::string sopClassUid_; + std::string sopInstanceUid_; + + void Setup(DcmFileFormat* dicom) + { + dicom_.reset(dicom); + + if (dicom == NULL || + dicom_->getDataset() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + DcmDataset& dataset = *dicom_->getDataset(); + index_.reset(new DicomFrameIndex(dataset)); + + E_TransferSyntax xfer = dataset.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + dataset.updateOriginalXfer(); + xfer = dataset.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax of the DICOM instance"); + } + } + + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer)) + { + throw OrthancException( + ErrorCode_BadFileFormat, + "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer)); + } + + const char* a = NULL; + const char* b = NULL; + + if (!dataset.findAndGetString(DCM_SOPClassUID, a).good() || + !dataset.findAndGetString(DCM_SOPInstanceUID, b).good() || + a == NULL || + b == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing SOP class/instance UID in DICOM instance"); + } + + sopClassUid_.assign(a); + sopInstanceUid_.assign(b); + } + + public: + DcmtkTranscoder(DcmFileFormat* dicom) // Takes ownership + { + Setup(dicom); + } + + DcmtkTranscoder(const void* dicom, + size_t size) + { + Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size)); + } + + virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE + { + return transferSyntax_; + } + + virtual std::string GetSopClassUid() ORTHANC_OVERRIDE + { + return sopClassUid_; + } + + virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE + { + return sopInstanceUid_; + } + + virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE + { + return index_->GetFramesCount(); + } + + virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE + { + assert(dicom_->getDataset() != NULL); + return DicomImageDecoder::Decode(*dicom_->getDataset(), frame); + } + + virtual void GetCompressedFrame(std::string& target, + unsigned int frame) ORTHANC_OVERRIDE + { +#if 1 + index_->GetRawFrame(target, frame); + printf("%d: %d\n", frame, target.size()); +#endif + +#if 1 + assert(dicom_->getDataset() != NULL); + DcmDataset& dataset = *dicom_->getDataset(); + + DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); + + if (pixelSequence != NULL && + frame == 0 && + pixelSequence->card() != GetFramesCount() + 1) + { + printf("COMPRESSED\n"); + + // Check out "djcodecd.cc" + + printf("%d fragments\n", pixelSequence->card()); + + // Skip the first fragment, that is the offset table + for (unsigned long i = 1; ;i++) + { + DcmPixelItem *fragment = NULL; + if (pixelSequence->getItem(fragment, i).good()) + { + printf("fragment %d %d\n", i, fragment->getLength()); + } + else + { + break; + } + } + } +#endif + } + + virtual IDicomTranscoder* Transcode(std::set<DicomTransferSyntax> syntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } + }; +} + + + + +static bool Transcode(std::string& buffer, + DcmDataset& dataSet, + E_TransferSyntax xfer) +{ + // Determine the transfer syntax which shall be used to write the + // information to the file. We always switch to the Little Endian + // syntax, with explicit length. + + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + + + /** + * Note that up to Orthanc 0.7.1 (inclusive), the + * "EXS_LittleEndianExplicit" was always used to save the DICOM + * dataset into memory. We now keep the original transfer syntax + * (if available). + **/ + //E_TransferSyntax xfer = dataSet.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + // No information about the original transfer syntax: This is + // most probably a DICOM dataset that was read from memory. + xfer = EXS_LittleEndianExplicit; + } + + E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; + + // Create the meta-header information + DcmFileFormat ff(&dataSet); + ff.validateMetaInfo(xfer); + ff.removeInvalidGroups(); + + // Create a memory buffer with the proper size + { + const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) + buffer.resize(estimatedSize); + } + + DcmOutputBufferStream ob(&buffer[0], buffer.size()); + + // Fill the memory buffer with the meta-header and the dataset + ff.transferInit(); + OFCondition c = ff.write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_withoutPadding); + ff.transferEnd(); + + if (c.good()) + { + // The DICOM file is successfully written, truncate the target + // buffer if its size was overestimated by (*) + ob.flush(); + + size_t effectiveSize = static_cast<size_t>(ob.tell()); + if (effectiveSize < buffer.size()) + { + buffer.resize(effectiveSize); + } + + return true; + } + else + { + // Error + buffer.clear(); + return false; + } +} + +#include "dcmtk/dcmjpeg/djrploss.h" /* for DJ_RPLossy */ +#include "dcmtk/dcmjpeg/djrplol.h" /* for DJ_RPLossless */ + +#include <boost/filesystem.hpp> + + +static void TestFile(const std::string& path) +{ + printf("** %s\n", path.c_str()); + + std::string s; + SystemToolbox::ReadFile(s, path); + + Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size()); + + printf("[%s] [%s] [%s] %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), + transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(), + transcoder.GetFramesCount()); + + for (size_t i = 0; i < transcoder.GetFramesCount(); i++) + { + std::string f; + transcoder.GetCompressedFrame(f, i); + + if (i == 0) + { + static unsigned int i = 0; + char buf[1024]; + sprintf(buf, "/tmp/frame-%06d.dcm", i++); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(f, buf); + } + } + + printf("\n"); +} + +TEST(Toto, Transcode) +{ + if (0) + { + OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); + + std::string s; + //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm"); + //SystemToolbox::ReadFile(s, "/home/jodogne/DICOM/Alain.dcm"); + SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm"); + + std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size())); + + // less /home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcxfer.h + printf(">> %d\n", dicom->getDataset()->getOriginalXfer()); // => 4 == EXS_JPEGProcess1 + + const DcmRepresentationParameter *p; + +#if 0 + E_TransferSyntax target = EXS_LittleEndianExplicit; + p = NULL; +#elif 1 + E_TransferSyntax target = EXS_JPEGProcess14SV1; + DJ_RPLossless rp_lossless(6, 0); + p = &rp_lossless; +#else + E_TransferSyntax target = EXS_JPEGProcess1; + DJ_RPLossy rp_lossy(90); // quality + p = &rp_lossy; +#endif + + ASSERT_TRUE(dicom->getDataset()->chooseRepresentation(target, p).good()); + ASSERT_TRUE(dicom->getDataset()->canWriteXfer(target)); + + std::string t; + ASSERT_TRUE(Transcode(t, *dicom->getDataset(), target)); + + SystemToolbox::WriteFile(s, "source.dcm"); + SystemToolbox::WriteFile(t, "target.dcm"); + } + + if (1) + { + const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes"; + + for (boost::filesystem::directory_iterator it(PATH); + it != boost::filesystem::directory_iterator(); ++it) + { + if (boost::filesystem::is_regular_file(it->status())) + { + TestFile(it->path().string()); + } + } + + TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm"); + TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm"); + } +} + +#endif
--- a/UnitTestsSources/ImageProcessingTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/ImageProcessingTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/Compatibility.h" #include "../Core/DicomFormat/DicomImageInformation.h" #include "../Core/Images/Image.h" #include "../Core/Images/ImageProcessing.h" @@ -92,7 +93,7 @@ class TestImageTraits : public ::testing::Test { private: - std::auto_ptr<Image> image_; + std::unique_ptr<Image> image_; protected: virtual void SetUp() ORTHANC_OVERRIDE @@ -282,6 +283,44 @@ return p == value; } +static void SetGrayscale16Pixel(ImageAccessor& image, + unsigned int x, + unsigned int y, + uint16_t value) +{ + ImageTraits<PixelFormat_Grayscale16>::SetPixel(image, value, x, y); +} + +static bool TestGrayscale16Pixel(const ImageAccessor& image, + unsigned int x, + unsigned int y, + uint16_t value) +{ + PixelTraits<PixelFormat_Grayscale16>::PixelType p; + ImageTraits<PixelFormat_Grayscale16>::GetPixel(p, image, x, y); + if (p != value) printf("%d %d\n", p, value); + return p == value; +} + +static void SetSignedGrayscale16Pixel(ImageAccessor& image, + unsigned int x, + unsigned int y, + int16_t value) +{ + ImageTraits<PixelFormat_SignedGrayscale16>::SetPixel(image, value, x, y); +} + +static bool TestSignedGrayscale16Pixel(const ImageAccessor& image, + unsigned int x, + unsigned int y, + int16_t value) +{ + PixelTraits<PixelFormat_SignedGrayscale16>::PixelType p; + ImageTraits<PixelFormat_SignedGrayscale16>::GetPixel(p, image, x, y); + if (p != value) printf("%d %d\n", p, value); + return p == value; +} + static void SetRGB24Pixel(ImageAccessor& image, unsigned int x, unsigned int y, @@ -509,7 +548,7 @@ SetGrayscale8Pixel(dirac, 4, 0, 100); { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0)); ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0)); @@ -523,7 +562,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0)); ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0)); @@ -537,7 +576,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0)); ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0)); @@ -557,7 +596,7 @@ SetGrayscale8Pixel(dirac, 0, 4, 100); { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0)); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0)); @@ -571,7 +610,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0)); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0)); @@ -585,7 +624,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0)); ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0)); @@ -605,7 +644,7 @@ SetRGB24Pixel(dirac, 4, 0, 100, 120, 140); { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0)); ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0)); @@ -619,7 +658,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0)); ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0)); @@ -633,7 +672,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0)); ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0)); @@ -653,7 +692,7 @@ SetRGB24Pixel(dirac, 0, 4, 100, 120, 140); { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0)); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0)); @@ -667,7 +706,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0)); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0)); @@ -681,7 +720,7 @@ } { - std::auto_ptr<ImageAccessor> image(Image::Clone(dirac)); + std::unique_ptr<ImageAccessor> image(Image::Clone(dirac)); ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0)); ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0)); @@ -774,3 +813,202 @@ ASSERT_TRUE(TestRGB24Pixel(image, 4, 4, 0, 0, 0)); } } + +TEST(ImageProcessing, ApplyWindowingFloatToGrayScale8) +{ + { + Image image(PixelFormat_Float32, 6, 1, false); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, -5.0f, 0, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 0.0f, 1, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 5.0f, 2, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 10.0f, 3, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 1000.0f, 4, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 2.0f, 5, 0); + + { + Image target(PixelFormat_Grayscale8, 6, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false); + + ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 128)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255*2/10)); + } + + { + Image target(PixelFormat_Grayscale8, 6, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, true); + + ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 127)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255 - 255*2/10)); + } + + { + Image target(PixelFormat_Grayscale8, 6, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 5000.0f, 10000.01f, 1000.0f, 0.0f, false); + + ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 128)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255*2/10)); + } + + { + Image target(PixelFormat_Grayscale8, 6, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 5000.0f, 10000.01f, 1000.0f, 0.0f, true); + + ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 255)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 127)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255 - 256*2/10)); + } + + { + Image target(PixelFormat_Grayscale8, 6, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 50.0f, 100.1f, 10.0f, 30.0f, false); + + ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 0)); // (-5 * 10) + 30 => pixel value = -20 => 0 + ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 256*30/100)); // ((0 * 10) + 30 => pixel value = 30 => 30% + ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 256*80/100)); // ((5 * 10) + 30 => pixel value = 80 => 80% + ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 255)); // ((10 * 10) + 30 => pixel value = 130 => 100% + ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 255)); // ((1000 * 10) + 30 => pixel value = 10030 => 100% + ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 128)); // ((2 * 10) + 30 => pixel value = 50 => 50% + } + + } +} + +TEST(ImageProcessing, ApplyWindowingFloatToGrayScale16) +{ + { + Image image(PixelFormat_Float32, 6, 1, false); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, -5.0f, 0, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 0.0f, 1, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 5.0f, 2, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 10.0f, 3, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 1000.0f, 4, 0); + ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 2.0f, 5, 0); + + { + Image target(PixelFormat_Grayscale16, 6, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false); + + ASSERT_TRUE(TestGrayscale16Pixel(target, 0, 0, 0)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 1, 0, 0)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 2, 0, 32768)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 3, 0, 65535)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 4, 0, 65535)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 5, 0, 65536*2/10)); + } + } +} + +TEST(ImageProcessing, ApplyWindowingGrayScale8ToGrayScale16) +{ + { + Image image(PixelFormat_Grayscale8, 5, 1, false); + SetGrayscale8Pixel(image, 0, 0, 0); + SetGrayscale8Pixel(image, 1, 0, 2); + SetGrayscale8Pixel(image, 2, 0, 5); + SetGrayscale8Pixel(image, 3, 0, 10); + SetGrayscale8Pixel(image, 4, 0, 255); + + { + Image target(PixelFormat_Grayscale16, 5, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false); + + ASSERT_TRUE(TestGrayscale16Pixel(target, 0, 0, 0)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 1, 0, 65536*2/10)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 2, 0, 65536*5/10)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 3, 0, 65535)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 4, 0, 65535)); + } + } +} + +TEST(ImageProcessing, ApplyWindowingGrayScale16ToGrayScale16) +{ + { + Image image(PixelFormat_Grayscale16, 5, 1, false); + SetGrayscale16Pixel(image, 0, 0, 0); + SetGrayscale16Pixel(image, 1, 0, 2); + SetGrayscale16Pixel(image, 2, 0, 5); + SetGrayscale16Pixel(image, 3, 0, 10); + SetGrayscale16Pixel(image, 4, 0, 255); + + { + Image target(PixelFormat_Grayscale16, 5, 1, false); + ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false); + + ASSERT_TRUE(TestGrayscale16Pixel(target, 0, 0, 0)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 1, 0, 65536*2/10)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 2, 0, 65536*5/10)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 3, 0, 65535)); + ASSERT_TRUE(TestGrayscale16Pixel(target, 4, 0, 65535)); + } + } +} + + +TEST(ImageProcessing, ShiftScaleGrayscale8) +{ + Image image(PixelFormat_Grayscale8, 5, 1, false); + SetGrayscale8Pixel(image, 0, 0, 0); + SetGrayscale8Pixel(image, 1, 0, 2); + SetGrayscale8Pixel(image, 2, 0, 5); + SetGrayscale8Pixel(image, 3, 0, 10); + SetGrayscale8Pixel(image, 4, 0, 255); + + ImageProcessing::ShiftScale(image, -1.1, 1.5, true); + ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 0)); + ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 0, 1)); + ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 0, 6)); + ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 0, 13)); + ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 0, 255)); +} + + +TEST(ImageProcessing, ShiftScaleGrayscale16) +{ + Image image(PixelFormat_Grayscale16, 5, 1, false); + SetGrayscale16Pixel(image, 0, 0, 0); + SetGrayscale16Pixel(image, 1, 0, 2); + SetGrayscale16Pixel(image, 2, 0, 5); + SetGrayscale16Pixel(image, 3, 0, 10); + SetGrayscale16Pixel(image, 4, 0, 255); + + ImageProcessing::ShiftScale(image, -1.1, 1.5, true); + ASSERT_TRUE(TestGrayscale16Pixel(image, 0, 0, 0)); + ASSERT_TRUE(TestGrayscale16Pixel(image, 1, 0, 1)); + ASSERT_TRUE(TestGrayscale16Pixel(image, 2, 0, 6)); + ASSERT_TRUE(TestGrayscale16Pixel(image, 3, 0, 13)); + ASSERT_TRUE(TestGrayscale16Pixel(image, 4, 0, 381)); +} + + +TEST(ImageProcessing, ShiftScaleSignedGrayscale16) +{ + Image image(PixelFormat_SignedGrayscale16, 5, 1, false); + SetSignedGrayscale16Pixel(image, 0, 0, 0); + SetSignedGrayscale16Pixel(image, 1, 0, 2); + SetSignedGrayscale16Pixel(image, 2, 0, 5); + SetSignedGrayscale16Pixel(image, 3, 0, 10); + SetSignedGrayscale16Pixel(image, 4, 0, 255); + + ImageProcessing::ShiftScale(image, -17.1, 11.5, true); + ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 0, 0, -197)); + ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 1, 0, -174)); + ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 2, 0, -139)); + ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 3, 0, -82)); + ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 4, 0, 2736)); +}
--- a/UnitTestsSources/ImageTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/ImageTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/JpegLosslessTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/JpegLosslessTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/LoggingTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/LoggingTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/LuaTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/LuaTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/MemoryCacheTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/MemoryCacheTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -40,9 +40,11 @@ #include <boost/lexical_cast.hpp> #include "../Core/Cache/MemoryCache.h" +#include "../Core/Cache/MemoryStringCache.h" #include "../Core/Cache/SharedArchive.h" #include "../Core/IDynamicObject.h" #include "../Core/Logging.h" +#include "../OrthancServer/StorageCommitmentReports.h" TEST(LRU, Basic) @@ -212,7 +214,7 @@ } }; - class IntegerProvider : public Orthanc::ICachePageProvider + class IntegerProvider : public Orthanc::Deprecated::ICachePageProvider { public: std::string log_; @@ -231,7 +233,7 @@ IntegerProvider provider; { - Orthanc::MemoryCache cache(provider, 3); + Orthanc::Deprecated::MemoryCache cache(provider, 3); cache.Access("42"); // 42 -> exit cache.Access("43"); // 43, 42 -> exit cache.Access("45"); // 45, 43, 42 -> exit @@ -317,3 +319,142 @@ ASSERT_EQ(2u, count); } + + +TEST(MemoryStringCache, Basic) +{ + Orthanc::MemoryStringCache c; + ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException); + + c.SetMaximumSize(2); + + std::string v; + ASSERT_FALSE(c.Fetch(v, "hello")); + + c.Add("hello", "a"); + ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); + ASSERT_FALSE(c.Fetch(v, "hello2")); + ASSERT_FALSE(c.Fetch(v, "hello3")); + + c.Add("hello2", "b"); + ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); + ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); + ASSERT_FALSE(c.Fetch(v, "hello3")); + + c.Add("hello3", "too large value"); + ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); + ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); + ASSERT_FALSE(c.Fetch(v, "hello3")); + + c.Add("hello3", "c"); + ASSERT_FALSE(c.Fetch(v, "hello")); // Recycled + ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); + ASSERT_TRUE(c.Fetch(v, "hello3")); ASSERT_EQ("c", v); +} + + +TEST(MemoryStringCache, Invalidate) +{ + Orthanc::MemoryStringCache c; + c.Add("hello", "a"); + c.Add("hello2", "b"); + + std::string v; + ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); + ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); + + c.Invalidate("hello"); + ASSERT_FALSE(c.Fetch(v, "hello")); + ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); +} + + +TEST(StorageCommitmentReports, Basic) +{ + Orthanc::StorageCommitmentReports reports(2); + ASSERT_EQ(2u, reports.GetMaxSize()); + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "nope"); + ASSERT_EQ("nope", accessor.GetTransactionUid()); + ASSERT_FALSE(accessor.IsValid()); + ASSERT_THROW(accessor.GetReport(), Orthanc::OrthancException); + } + + reports.Store("a", new Orthanc::StorageCommitmentReports::Report("aet_a")); + reports.Store("b", new Orthanc::StorageCommitmentReports::Report("aet_b")); + reports.Store("c", new Orthanc::StorageCommitmentReports::Report("aet_c")); + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a"); + ASSERT_FALSE(accessor.IsValid()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b"); + ASSERT_TRUE(accessor.IsValid()); + ASSERT_EQ("aet_b", accessor.GetReport().GetRemoteAet()); + ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Pending, + accessor.GetReport().GetStatus()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c"); + ASSERT_EQ("aet_c", accessor.GetReport().GetRemoteAet()); + ASSERT_TRUE(accessor.IsValid()); + } + + { + std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report + (new Orthanc::StorageCommitmentReports::Report("aet")); + report->AddSuccess("class1", "instance1"); + report->AddFailure("class2", "instance2", + Orthanc::StorageCommitmentFailureReason_ReferencedSOPClassNotSupported); + report->MarkAsComplete(); + reports.Store("a", report.release()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a"); + ASSERT_TRUE(accessor.IsValid()); + ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet()); + ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Failure, + accessor.GetReport().GetStatus()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b"); + ASSERT_FALSE(accessor.IsValid()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c"); + ASSERT_TRUE(accessor.IsValid()); + } + + { + std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report + (new Orthanc::StorageCommitmentReports::Report("aet")); + report->AddSuccess("class1", "instance1"); + report->MarkAsComplete(); + reports.Store("a", report.release()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a"); + ASSERT_TRUE(accessor.IsValid()); + ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet()); + ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Success, + accessor.GetReport().GetStatus()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b"); + ASSERT_FALSE(accessor.IsValid()); + } + + { + Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c"); + ASSERT_TRUE(accessor.IsValid()); + } +}
--- a/UnitTestsSources/MultiThreadingTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/MultiThreadingTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/Compatibility.h" #include "../Core/FileStorage/MemoryStorageArea.h" #include "../Core/JobsEngine/JobsEngine.h" #include "../Core/Logging.h" @@ -101,7 +102,7 @@ { } - virtual JobStepResult Step() ORTHANC_OVERRIDE + virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE { if (fails_) { @@ -272,7 +273,7 @@ q.Enqueue(new DynamicInteger(30, s)); q.Enqueue(new DynamicInteger(40, s)); - std::auto_ptr<DynamicInteger> i; + std::unique_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()); @@ -747,7 +748,7 @@ SequenceOfOperationsJob* job = NULL; { - std::auto_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob); + std::unique_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob); job = a.get(); engine.GetRegistry().Submit(id, a.release(), 0); } @@ -837,7 +838,7 @@ } else { - std::auto_ptr<IJob> unserialized(unserializer.UnserializeJob(a)); + std::unique_ptr<IJob> unserialized(unserializer.UnserializeJob(a)); Json::Value b = 43; if (unserialized->Serialize(b)) @@ -863,7 +864,7 @@ } else { - std::auto_ptr<SetOfInstancesJob> unserialized + std::unique_ptr<SetOfInstancesJob> unserialized (dynamic_cast<SetOfInstancesJob*>(unserializer.UnserializeJob(a))); Json::Value b = 43; @@ -889,7 +890,7 @@ Json::Value a = 42; operation.Serialize(a); - std::auto_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a)); + std::unique_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a)); Json::Value b = 43; unserialized->Serialize(b); @@ -904,7 +905,7 @@ Json::Value a = 42; value.Serialize(a); - std::auto_ptr<JobOperationValue> unserialized(unserializer.UnserializeValue(a)); + std::unique_ptr<JobOperationValue> unserialized(unserializer.UnserializeValue(a)); Json::Value b = 43; unserialized->Serialize(b); @@ -957,7 +958,7 @@ { GenericJobUnserializer unserializer; - std::auto_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s)); + std::unique_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s)); ASSERT_EQ(3u, values->GetSize()); ASSERT_EQ(JobOperationValue::Type_Null, values->GetValue(0).GetType()); ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(1).GetType()); @@ -984,7 +985,7 @@ ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - std::auto_ptr<JobOperationValue> value; + std::unique_ptr<JobOperationValue> value; value.reset(unserializer.UnserializeValue(s)); ASSERT_EQ(JobOperationValue::Type_Null, value->GetType()); @@ -1021,7 +1022,7 @@ ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); { - std::auto_ptr<IJobOperation> operation; + std::unique_ptr<IJobOperation> operation; operation.reset(unserializer.UnserializeOperation(s)); // Make sure that we have indeed unserialized a log operation @@ -1045,12 +1046,12 @@ job.AddInstance("nope"); job.AddInstance("world"); job.SetPermissive(true); - ASSERT_THROW(job.Step(), OrthancException); // Not started yet + ASSERT_THROW(job.Step("jobId"), OrthancException); // Not started yet ASSERT_FALSE(job.HasTrailingStep()); ASSERT_FALSE(job.IsTrailingStepDone()); job.Start(); - ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); - ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); { DummyUnserializer unserializer; @@ -1065,7 +1066,7 @@ ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - std::auto_ptr<IJob> job; + std::unique_ptr<IJob> job; job.reset(unserializer.UnserializeJob(s)); const DummyInstancesJob& tmp = dynamic_cast<const DummyInstancesJob&>(*job); @@ -1101,7 +1102,7 @@ lock.SetTrailingOperationTimeout(300); } - ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); { GenericJobUnserializer unserializer; @@ -1116,7 +1117,7 @@ ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); - std::auto_ptr<IJob> job; + std::unique_ptr<IJob> job; job.reset(unserializer.UnserializeJob(s)); std::string tmp; @@ -1143,11 +1144,11 @@ Json::Value s; ParsedDicomFile source(true); - source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false); - source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false); - source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false); + source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false, ""); + source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false, ""); + source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false, ""); - std::auto_ptr<ParsedDicomFile> modified(source.Clone(true)); + std::unique_ptr<ParsedDicomFile> modified(source.Clone(true)); { DicomModification modification; @@ -1166,7 +1167,7 @@ DicomModification modification(s); ASSERT_EQ(ResourceType_Series, modification.GetLevel()); - std::auto_ptr<ParsedDicomFile> second(source.Clone(true)); + std::unique_ptr<ParsedDicomFile> second(source.Clone(true)); modification.Apply(*second); std::string s; @@ -1282,7 +1283,7 @@ private: MemoryStorageArea storage_; SQLiteDatabaseWrapper db_; // The SQLite DB is in memory - std::auto_ptr<ServerContext> context_; + std::unique_ptr<ServerContext> context_; TimeoutDicomConnectionManager manager_; public: @@ -1310,7 +1311,7 @@ // Create a sample DICOM file ParsedDicomFile dicom(true); dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"), - false, DicomReplaceMode_InsertIfAbsent); + false, DicomReplaceMode_InsertIfAbsent, ""); DicomInstanceToStore toStore; toStore.SetParsedDicomFile(dicom); @@ -1336,7 +1337,7 @@ instance.Serialize(s); } - std::auto_ptr<JobOperationValue> value; + std::unique_ptr<JobOperationValue> value; value.reset(unserializer.UnserializeValue(s)); ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType()); ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId()); @@ -1369,7 +1370,7 @@ operation.Serialize(s); } - std::auto_ptr<IJobOperation> operation; + std::unique_ptr<IJobOperation> operation; { operation.reset(unserializer.UnserializeOperation(s)); @@ -1411,7 +1412,9 @@ modality.SetHost("192.168.1.1"); modality.SetPortNumber(1000); modality.SetManufacturer(ModalityManufacturer_StoreScp); - modality.SetPreferredTransferSyntax("1.2.840.10008.1.2"); + ASSERT_EQ("1.2.840.10008.1.2", modality.GetPreferredTransferSyntax()); + modality.SetPreferredTransferSyntax("1.2.840.10008.1.2.1"); + ASSERT_EQ("1.2.840.10008.1.2.1", modality.GetPreferredTransferSyntax()); StoreScuOperation operation("TEST", modality); @@ -1457,7 +1460,7 @@ // ModifyInstanceOperation { - std::auto_ptr<DicomModification> modification(new DicomModification); + std::unique_ptr<DicomModification> modification(new DicomModification); modification->SetupAnonymization(DicomVersion_2008); ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release()); @@ -1497,7 +1500,9 @@ modality.SetHost("192.168.1.1"); modality.SetPortNumber(1000); modality.SetManufacturer(ModalityManufacturer_StoreScp); - modality.SetPreferredTransferSyntax("1.2.840.10008.1.2"); + ASSERT_EQ("1.2.840.10008.1.2", modality.GetPreferredTransferSyntax()); + modality.SetPreferredTransferSyntax("1.2.840.10008.1.2.1"); + ASSERT_EQ("1.2.840.10008.1.2.1", modality.GetPreferredTransferSyntax()); DicomModalityStoreJob job(GetContext()); job.SetLocalAet("LOCAL"); @@ -1509,7 +1514,7 @@ } { - std::auto_ptr<IJob> job; + std::unique_ptr<IJob> job; job.reset(unserializer.UnserializeJob(s)); DicomModalityStoreJob& tmp = dynamic_cast<DicomModalityStoreJob&>(*job); @@ -1539,7 +1544,7 @@ } { - std::auto_ptr<IJob> job; + std::unique_ptr<IJob> job; job.reset(unserializer.UnserializeJob(s)); OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job); @@ -1552,7 +1557,7 @@ // ResourceModificationJob { - std::auto_ptr<DicomModification> modification(new DicomModification); + std::unique_ptr<DicomModification> modification(new DicomModification); modification->SetupAnonymization(DicomVersion_2008); ResourceModificationJob job(GetContext()); @@ -1564,7 +1569,7 @@ } { - std::auto_ptr<IJob> job; + std::unique_ptr<IJob> job; job.reset(unserializer.UnserializeJob(s)); ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job); @@ -1620,8 +1625,8 @@ job.AddTrailingStep(); job.Start(); - ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); - ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); study2 = job.GetTargetStudy(); ASSERT_FALSE(study2.empty()); @@ -1631,7 +1636,7 @@ } { - std::auto_ptr<IJob> job; + std::unique_ptr<IJob> job; job.reset(unserializer.UnserializeJob(s)); SplitStudyJob& tmp = dynamic_cast<SplitStudyJob&>(*job); @@ -1679,8 +1684,8 @@ job.AddTrailingStep(); job.Start(); - ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); - ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); ASSERT_TRUE(job.Serialize(s)); @@ -1695,7 +1700,7 @@ } { - std::auto_ptr<IJob> job; + std::unique_ptr<IJob> job; job.reset(unserializer.UnserializeJob(s)); MergeStudyJob& tmp = dynamic_cast<MergeStudyJob&>(*job); @@ -1748,7 +1753,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); ASSERT_EQ(1u, job.GetPosition()); ASSERT_FALSE(job.IsTrailingStepDone()); @@ -1757,7 +1762,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_THROW(job.Step(), OrthancException); + ASSERT_THROW(job.Step("jobId"), OrthancException); } { @@ -1779,7 +1784,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); ASSERT_EQ(1u, job.GetPosition()); ASSERT_FALSE(job.IsTrailingStepDone()); @@ -1788,7 +1793,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); ASSERT_EQ(2u, job.GetPosition()); ASSERT_FALSE(job.IsTrailingStepDone()); @@ -1797,7 +1802,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_THROW(job.Step(), OrthancException); + ASSERT_THROW(job.Step("jobId"), OrthancException); } { @@ -1820,7 +1825,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); ASSERT_EQ(1u, job.GetPosition()); ASSERT_TRUE(job.IsTrailingStepDone()); @@ -1829,7 +1834,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_THROW(job.Step(), OrthancException); + ASSERT_THROW(job.Step("jobId"), OrthancException); } { @@ -1854,7 +1859,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode()); ASSERT_EQ(1u, job.GetPosition()); ASSERT_FALSE(job.IsTrailingStepDone()); @@ -1863,7 +1868,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode()); ASSERT_EQ(2u, job.GetPosition()); ASSERT_TRUE(job.IsTrailingStepDone()); @@ -1872,7 +1877,7 @@ ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); } - ASSERT_THROW(job.Step(), OrthancException); + ASSERT_THROW(job.Step("jobId"), OrthancException); } } @@ -1899,6 +1904,8 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); } s = Json::nullValue; @@ -1911,8 +1918,10 @@ modality.SetApplicationEntityTitle("HELLO"); modality.SetHost("world"); modality.SetPortNumber(45); - modality.SetManufacturer(ModalityManufacturer_Dcm4Chee); - modality.SetPreferredTransferSyntax("1.2.840.10008.1.2"); + modality.SetManufacturer(ModalityManufacturer_GenericNoWildcardInDates); + ASSERT_EQ("1.2.840.10008.1.2", modality.GetPreferredTransferSyntax()); + modality.SetPreferredTransferSyntax("1.2.840.10008.1.2.1"); + ASSERT_EQ("1.2.840.10008.1.2.1", modality.GetPreferredTransferSyntax()); modality.Serialize(s, true); ASSERT_EQ(Json::objectValue, s.type()); } @@ -1922,12 +1931,14 @@ ASSERT_EQ("HELLO", modality.GetApplicationEntityTitle()); ASSERT_EQ("world", modality.GetHost()); ASSERT_EQ(45u, modality.GetPortNumber()); - ASSERT_EQ(ModalityManufacturer_Dcm4Chee, modality.GetManufacturer()); + ASSERT_EQ(ModalityManufacturer_GenericNoWildcardInDates, modality.GetManufacturer()); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); } s["Port"] = "46"; @@ -1947,8 +1958,10 @@ operations.insert(DicomRequestType_Get); operations.insert(DicomRequestType_Move); operations.insert(DicomRequestType_Store); + operations.insert(DicomRequestType_NAction); + operations.insert(DicomRequestType_NEventReport); - ASSERT_EQ(5u, operations.size()); + ASSERT_EQ(7u, operations.size()); for (std::set<DicomRequestType>::const_iterator it = operations.begin(); it != operations.end(); ++it) @@ -1977,4 +1990,54 @@ } } } + + { + Json::Value s; + s["AllowStorageCommitment"] = false; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + } + + { + Json::Value s; + s["AllowNAction"] = false; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + } + + { + Json::Value s; + s["AllowNAction"] = true; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + } }
--- a/UnitTestsSources/PluginsTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/PluginsTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/PrecompiledHeadersUnitTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/PrecompiledHeadersUnitTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/PrecompiledHeadersUnitTests.h Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/PrecompiledHeadersUnitTests.h Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/RestApiTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/RestApiTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/SQLiteChromiumTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/SQLiteChromiumTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/SQLiteTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/SQLiteTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/ServerIndexTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/ServerIndexTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -34,6 +34,7 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/Compatibility.h" #include "../Core/FileStorage/FilesystemStorage.h" #include "../Core/FileStorage/MemoryStorageArea.h" #include "../Core/Logging.h" @@ -95,8 +96,8 @@ class DatabaseWrapperTest : public ::testing::Test { protected: - std::auto_ptr<TestDatabaseListener> listener_; - std::auto_ptr<SQLiteDatabaseWrapper> index_; + std::unique_ptr<TestDatabaseListener> listener_; + std::unique_ptr<SQLiteDatabaseWrapper> index_; public: DatabaseWrapperTest()
--- a/UnitTestsSources/StreamTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/StreamTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/ToolboxTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/ToolboxTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -33,6 +33,8 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/Compatibility.h" +#include "../Core/IDynamicObject.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" @@ -134,3 +136,36 @@ printf("decoding took %zu ms\n", (std::chrono::duration_cast<std::chrono::milliseconds>(afterDecoding - afterEncoding))); } #endif + + +TEST(Toolbox, LargeHexadecimalToDecimal) +{ + // https://stackoverflow.com/a/16967286/881731 + ASSERT_EQ( + "166089946137986168535368849184301740204613753693156360462575217560130904921953976324839782808018277000296027060873747803291797869684516494894741699267674246881622658654267131250470956587908385447044319923040838072975636163137212887824248575510341104029461758594855159174329892125993844566497176102668262139513", + Toolbox::LargeHexadecimalToDecimal("EC851A69B8ACD843164E10CFF70CF9E86DC2FEE3CF6F374B43C854E3342A2F1AC3E30C741CC41E679DF6D07CE6FA3A66083EC9B8C8BF3AF05D8BDBB0AA6Cb3ef8c5baa2a5e531ba9e28592f99e0fe4f95169a6c63f635d0197e325c5ec76219b907e4ebdcd401fb1986e4e3ca661ff73e7e2b8fd9988e753b7042b2bbca76679")); + + ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal("")); + ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal("0")); + ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal("0000")); + ASSERT_EQ("255", Toolbox::LargeHexadecimalToDecimal("00000ff")); + + ASSERT_THROW(Toolbox::LargeHexadecimalToDecimal("g"), Orthanc::OrthancException); +} + + +TEST(Toolbox, GenerateDicomPrivateUniqueIdentifier) +{ + std::string s = Toolbox::GenerateDicomPrivateUniqueIdentifier(); + ASSERT_EQ("2.25.", s.substr(0, 5)); +} + + +TEST(Toolbox, UniquePtr) +{ + std::unique_ptr<int> i(new int(42)); + ASSERT_EQ(42, *i); + + std::unique_ptr<SingleValueObject<int> > j(new SingleValueObject<int>(42)); + ASSERT_EQ(42, j->GetValue()); +}
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/UnitTestsMain.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -630,13 +630,13 @@ ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("GenericNoWildcardInDates"))); ASSERT_STREQ("GenericNoUniversalWildcard", EnumerationToString(StringToModalityManufacturer("GenericNoUniversalWildcard"))); ASSERT_STREQ("StoreScp", EnumerationToString(StringToModalityManufacturer("StoreScp"))); - ASSERT_STREQ("ClearCanvas", EnumerationToString(StringToModalityManufacturer("ClearCanvas"))); - ASSERT_STREQ("Dcm4Chee", EnumerationToString(StringToModalityManufacturer("Dcm4Chee"))); ASSERT_STREQ("Vitrea", EnumerationToString(StringToModalityManufacturer("Vitrea"))); ASSERT_STREQ("GE", EnumerationToString(StringToModalityManufacturer("GE"))); // backward compatibility tests (to remove once we make these manufacturer really obsolete) ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("MedInria"))); ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("EFilm2"))); + ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("ClearCanvas"))); + ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("Dcm4Chee"))); ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("SyngoVia"))); ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("AgfaImpax"))); } @@ -1414,6 +1414,7 @@ int main(int argc, char **argv) { Logging::Initialize(); + Toolbox::InitializeGlobalLocale(NULL); Logging::EnableInfoLevel(true); Toolbox::DetectEndianness(); SystemToolbox::MakeDirectory("UnitTestsResults");
--- a/UnitTestsSources/VersionsTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/VersionsTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -88,8 +88,15 @@ #else // http://www.sqlite.org/capi3ref.html#sqlite3_libversion EXPECT_EQ(sqlite3_libversion_number(), SQLITE_VERSION_NUMBER); - EXPECT_STREQ(sqlite3_sourceid(), SQLITE_SOURCE_ID); EXPECT_STREQ(sqlite3_libversion(), SQLITE_VERSION); + + /** + * On Orthanc > 1.5.8, we comment out the following test, that is + * too strict for some GNU/Linux distributions to apply their own + * security fixes. Checking the main version macros is sufficient. + * https://bugzilla.suse.com/show_bug.cgi?id=1154550#c2 + **/ + // EXPECT_STREQ(sqlite3_sourceid(), SQLITE_SOURCE_ID); #endif // Ensure that the SQLite version is above 3.7.0. @@ -106,6 +113,14 @@ } +#if ORTHANC_ENABLE_CIVETWEB == 1 +TEST(Version, CivetwebCompression) +{ + ASSERT_TRUE(mg_check_feature(MG_FEATURES_COMPRESSION)); +} +#endif + + #if ORTHANC_STATIC == 1 TEST(Versions, ZlibStatic) @@ -169,7 +184,8 @@ #if ORTHANC_ENABLE_SSL == 1 TEST(Version, OpenSslStatic) { - ASSERT_EQ(0x1000210fL /* openssl-1.0.2p */, OPENSSL_VERSION_NUMBER); + ASSERT_TRUE(OPENSSL_VERSION_NUMBER == 0x1000210fL /* openssl-1.0.2p */ || + OPENSSL_VERSION_NUMBER == 0x1010104fL /* openssl-1.1.1d */); } #endif
--- a/UnitTestsSources/ZipTests.cpp Wed Mar 18 08:59:06 2020 +0100 +++ b/UnitTestsSources/ZipTests.cpp Thu Mar 19 11:48:30 2020 +0100 @@ -2,7 +2,7 @@ * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as