Mercurial > hg > orthanc
changeset 2884:497a637366b4 db-changes
integration mainline->db-changes
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 12 Oct 2018 15:18:10 +0200 |
parents | 2b91363cc1d1 (current diff) 320a877a1f40 (diff) |
children | ce310baccda6 |
files | Core/ICommand.h Core/MultiThreading/ILockable.h Core/MultiThreading/Locker.h Core/MultiThreading/Mutex.cpp Core/MultiThreading/Mutex.h Core/MultiThreading/ReaderWriterLock.cpp Core/MultiThreading/ReaderWriterLock.h Core/MultiThreading/Semaphore.cpp Core/MultiThreading/Semaphore.h Core/Uuid.cpp Core/Uuid.h OrthancServer/DatabaseWrapperBase.cpp OrthancServer/DatabaseWrapperBase.h OrthancServer/DicomDirWriter.cpp OrthancServer/DicomDirWriter.h OrthancServer/DicomModification.cpp OrthancServer/DicomModification.h OrthancServer/DicomProtocol/DicomFindAnswers.cpp OrthancServer/DicomProtocol/DicomFindAnswers.h OrthancServer/DicomProtocol/DicomServer.cpp OrthancServer/DicomProtocol/DicomServer.h OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/DicomProtocol/DicomUserConnection.h OrthancServer/DicomProtocol/IApplicationEntityFilter.h OrthancServer/DicomProtocol/IFindRequestHandler.h OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h OrthancServer/DicomProtocol/IMoveRequestHandler.h OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h OrthancServer/DicomProtocol/IStoreRequestHandler.h OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h OrthancServer/DicomProtocol/RemoteModalityParameters.cpp OrthancServer/DicomProtocol/RemoteModalityParameters.h OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp OrthancServer/DicomProtocol/ReusableDicomUserConnection.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/Internals/CommandDispatcher.cpp OrthancServer/Internals/CommandDispatcher.h OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/DicomImageDecoder.h OrthancServer/Internals/FindScp.cpp OrthancServer/Internals/FindScp.h OrthancServer/Internals/MoveScp.cpp OrthancServer/Internals/MoveScp.h OrthancServer/Internals/StoreScp.cpp OrthancServer/Internals/StoreScp.h OrthancServer/OrthancPeerParameters.cpp OrthancServer/OrthancPeerParameters.h OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/Scheduler/CallSystemCommand.cpp OrthancServer/Scheduler/CallSystemCommand.h OrthancServer/Scheduler/DeleteInstanceCommand.cpp OrthancServer/Scheduler/DeleteInstanceCommand.h OrthancServer/Scheduler/IServerCommand.h OrthancServer/Scheduler/ModifyInstanceCommand.cpp OrthancServer/Scheduler/ModifyInstanceCommand.h OrthancServer/Scheduler/ServerCommandInstance.cpp OrthancServer/Scheduler/ServerCommandInstance.h OrthancServer/Scheduler/ServerJob.cpp OrthancServer/Scheduler/ServerJob.h OrthancServer/Scheduler/ServerScheduler.cpp OrthancServer/Scheduler/ServerScheduler.h OrthancServer/Scheduler/StorePeerCommand.cpp OrthancServer/Scheduler/StorePeerCommand.h OrthancServer/Scheduler/StoreScuCommand.cpp OrthancServer/Scheduler/StoreScuCommand.h OrthancServer/ToDcmtkBridge.cpp OrthancServer/ToDcmtkBridge.h Plugins/Engine/SharedLibrary.cpp Plugins/Engine/SharedLibrary.h Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Plugins/Samples/DatabasePlugin/CMakeLists.txt Plugins/Samples/DatabasePlugin/Database.cpp Plugins/Samples/DatabasePlugin/Database.h Plugins/Samples/DatabasePlugin/Plugin.cpp Plugins/Samples/GdcmDecoding/CMakeLists.txt Plugins/Samples/GdcmDecoding/OrthancContext.cpp Plugins/Samples/GdcmDecoding/OrthancContext.h Plugins/Samples/GdcmDecoding/Plugin.cpp Resources/CMake/GoogleLogConfiguration.cmake Resources/CMake/GoogleLogConfiguration.h Resources/CMake/GoogleLogConfigurationDarwin.h Resources/CMake/GoogleLogConfigurationLSB.h Resources/Patches/dcmtk-linux-speed.patch Resources/Patches/dcmtk-mingw64.patch Resources/Patches/dcmtk-mingw64.txt Resources/Patches/glog-port-cc.diff Resources/Patches/glog-port-h-v2.diff Resources/Patches/glog-port-h.diff Resources/Patches/glog-utilities-lsb.diff Resources/Patches/glog-utilities.diff Resources/Patches/glog-visual-studio-port.h Resources/Patches/log4cplus-patch.diff Resources/Patches/openssl-lsb.diff THANKS |
diffstat | 668 files changed, 77194 insertions(+), 27485 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,5 @@ +syntax: glob +ThirdPartyDownloads/ +CMakeLists.txt.user +*.cpp.orig +*.h.orig
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1 @@ +a95beca72e99f3a1110cffd252bcf3abf5a2db27 dcmtk-3.6.1
--- a/.travis.yml Thu Oct 29 11:25:45 2015 +0100 +++ b/.travis.yml Fri Oct 12 15:18:10 2018 +0200 @@ -31,20 +31,24 @@ -qq build-essential unzip cmake mercurial uuid-dev libcurl4-openssl-dev liblua5.1-0-dev libgtest-dev libpng-dev libsqlite3-dev libssl-dev zlib1g-dev libdcmtk2-dev libwrap0-dev libcharls-dev; fi + # For DCMTK 3.6.2 - Can't make it compile in static mode with MinGW32 on the + # Ubuntu Precise (12.04) that is used by Travis: + # - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then sudo apt-get install mingw-w64 gcc-mingw-w64-i686 g++-mingw-w64-i686 wine; fi + + # For DCMTK 3.6.0: - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then sudo apt-get install mingw32; fi - before_script: - mkdir Build - cd Build - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == OFF ]; then cmake -DCMAKE_BUILD_TYPE=Debug "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog" -DALLOW_DOWNLOADS=ON -DUSE_SYSTEM_BOOST=OFF -DUSE_SYSTEM_MONGOOSE=OFF -DUSE_SYSTEM_JSONCPP=OFF - -DUSE_SYSTEM_GOOGLE_LOG=OFF -DUSE_SYSTEM_PUGIXML=OFF -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON + -DUSE_SYSTEM_GOOGLE_LOG=OFF -DUSE_SYSTEM_PUGIXML=OFF -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON ..; fi - if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then cmake -DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON - -DCMAKE_TOOLCHAIN_FILE=Resources/MinGWToolchain.cmake + -DCMAKE_TOOLCHAIN_FILE=Resources/MinGWToolchain.cmake -DUSE_DCMTK_360=ON -DUSE_LEGACY_JSONCPP=ON ..; fi - if [ $TRAVIS_OS_NAME == osx ]; then cmake -DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON
--- a/AUTHORS Thu Oct 29 11:25:45 2015 +0100 +++ b/AUTHORS Fri Oct 12 15:18:10 2018 +0200 @@ -6,28 +6,15 @@ ------------------ * Sebastien Jodogne <s.jodogne@gmail.com> - Department of Medical Physics - University Hospital of Liege - Belgium Overall design and lead developer. - -Client library --------------- - -The client library of Orthanc is automatically wrapped from the C++ -code using the LAAW software. LAAW is the Lightweight, Automated API -Wrapper from the Jomago team, that comes from the following authors: +* Department of Medical Physics + University Hospital of Liege + 4000 Liege + Belgium -* Sebastien Jodogne <s.jodogne@gmail.com> -* Alain Mazy <alain@mazy.be> -* Benjamin Golinvaux <golinvauxb@gmail.com> - -LAAW should be soon released as a separate open-source project. - - -Contributors ------------- - -See the file "THANKS" for the occasional contributors. +* Osimis S.A. <info@osimis.io> + Rue du Bois Saint-Jean 15/1 + 4102 Seraing + Belgium
--- a/CMakeLists.txt Thu Oct 29 11:25:45 2015 +0100 +++ b/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -1,575 +1,525 @@ -cmake_minimum_required(VERSION 2.8) - -project(Orthanc) - -# Version of the build, should always be "mainline" except in release branches -set(ORTHANC_VERSION "mainline") - -# Version of the database schema. History: -# * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning -# * Orthanc 0.3.1 = version 2 -# * Orthanc 0.4.0 -> Orthanc 0.7.2 = version 3 -# * Orthanc 0.7.3 -> Orthanc 0.8.4 = version 4 -# * Orthanc 0.8.5 -> Orthanc 0.9.4 = version 5 -# * Orthanc 0.9.5 -> mainline = version 6 -set(ORTHANC_DATABASE_VERSION 6) - - -##################################################################### -## CMake parameters tunable at the command line -##################################################################### - -# Parameters of the build -SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") -SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") -SET(ENABLE_SSL ON CACHE BOOL "Include support for SSL") -SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") -SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") -SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") -SET(ENABLE_GOOGLE_LOG OFF CACHE BOOL "Enable Google Log (otherwise, an internal logger is used)") -SET(ENABLE_JPEG ON CACHE BOOL "Enable JPEG decompression") -SET(ENABLE_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression") -SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") -SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Build the ServeFolders plugin") - -# Advanced parameters to fine-tune linking against system libraries -SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") -SET(USE_SYSTEM_GOOGLE_LOG ON CACHE BOOL "Use the system version of Google Log") -SET(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") -SET(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite") -SET(USE_SYSTEM_MONGOOSE ON CACHE BOOL "Use the system version of Mongoose") -SET(USE_SYSTEM_LUA ON CACHE BOOL "Use the system version of Lua") -SET(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK") -SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") -SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng") -SET(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg") -SET(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl") -SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL") -SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib") -SET(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml)") - -# Experimental options -SET(USE_PUGIXML ON CACHE BOOL "Use the Pugixml parser (turn off only for debug)") - -# Distribution-specific settings -SET(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") -SET(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)") -SET(USE_BOOST_ICONV ON CACHE BOOL "Use iconv instead of wconv (Windows only)") - -mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE) -mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS) -mark_as_advanced(USE_BOOST_ICONV) -mark_as_advanced(USE_PUGIXML) - -# Path to the root folder of the Orthanc distribution -set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}) - -# Some basic inclusions -include(CheckIncludeFiles) -include(CheckIncludeFileCXX) -include(CheckLibraryExists) -include(FindPythonInterp) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/AutoGeneratedCode.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/Compiler.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake) - - - - -##################################################################### -## List of source files -##################################################################### - -set(ORTHANC_CORE_SOURCES - Core/Cache/MemoryCache.cpp - Core/Cache/SharedArchive.cpp - Core/ChunkedBuffer.cpp - Core/Compression/DeflateBaseCompressor.cpp - Core/Compression/GzipCompressor.cpp - Core/Compression/HierarchicalZipWriter.cpp - Core/Compression/ZipWriter.cpp - Core/Compression/ZlibCompressor.cpp - Core/DicomFormat/DicomArray.cpp - Core/DicomFormat/DicomMap.cpp - Core/DicomFormat/DicomTag.cpp - Core/DicomFormat/DicomImageInformation.cpp - Core/DicomFormat/DicomIntegerPixelAccessor.cpp - Core/DicomFormat/DicomInstanceHasher.cpp - Core/DicomFormat/DicomValue.cpp - Core/Enumerations.cpp - Core/FileStorage/FilesystemStorage.cpp - Core/FileStorage/StorageAccessor.cpp - Core/HttpClient.cpp - Core/HttpServer/BufferHttpSender.cpp - Core/HttpServer/EmbeddedResourceHttpHandler.cpp - Core/HttpServer/FilesystemHttpHandler.cpp - Core/HttpServer/HttpToolbox.cpp - Core/HttpServer/HttpOutput.cpp - Core/HttpServer/StringHttpOutput.cpp - Core/HttpServer/MongooseServer.cpp - Core/HttpServer/HttpFileSender.cpp - Core/HttpServer/FilesystemHttpSender.cpp - Core/HttpServer/HttpStreamTranscoder.cpp - Core/Logging.cpp - Core/RestApi/RestApiCall.cpp - Core/RestApi/RestApiGetCall.cpp - Core/RestApi/RestApiHierarchy.cpp - Core/RestApi/RestApiPath.cpp - Core/RestApi/RestApiOutput.cpp - Core/RestApi/RestApi.cpp - Core/MultiThreading/Mutex.cpp - Core/MultiThreading/ReaderWriterLock.cpp - Core/MultiThreading/RunnableWorkersPool.cpp - Core/MultiThreading/Semaphore.cpp - Core/MultiThreading/SharedMessageQueue.cpp - Core/Images/Font.cpp - Core/Images/FontRegistry.cpp - Core/Images/ImageAccessor.cpp - Core/Images/ImageBuffer.cpp - Core/Images/ImageProcessing.cpp - Core/Images/JpegErrorManager.cpp - Core/Images/JpegReader.cpp - Core/Images/JpegWriter.cpp - Core/Images/PngReader.cpp - Core/Images/PngWriter.cpp - Core/SQLite/Connection.cpp - Core/SQLite/FunctionContext.cpp - Core/SQLite/Statement.cpp - Core/SQLite/StatementId.cpp - Core/SQLite/StatementReference.cpp - Core/SQLite/Transaction.cpp - Core/Toolbox.cpp - Core/Uuid.cpp - Core/Lua/LuaContext.cpp - Core/Lua/LuaFunctionCall.cpp - ) - - -set(ORTHANC_SERVER_SOURCES - OrthancServer/DatabaseWrapper.cpp - OrthancServer/DatabaseWrapperBase.cpp - OrthancServer/DicomDirWriter.cpp - OrthancServer/DicomModification.cpp - OrthancServer/DicomProtocol/DicomFindAnswers.cpp - OrthancServer/DicomProtocol/DicomServer.cpp - OrthancServer/DicomProtocol/DicomUserConnection.cpp - OrthancServer/DicomProtocol/RemoteModalityParameters.cpp - OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp - OrthancServer/ExportedResource.cpp - OrthancServer/FromDcmtkBridge.cpp - OrthancServer/Internals/CommandDispatcher.cpp - OrthancServer/Internals/DicomImageDecoder.cpp - OrthancServer/Internals/FindScp.cpp - OrthancServer/Internals/MoveScp.cpp - OrthancServer/Internals/StoreScp.cpp - OrthancServer/LuaScripting.cpp - OrthancServer/OrthancFindRequestHandler.cpp - OrthancServer/OrthancHttpHandler.cpp - OrthancServer/OrthancInitialization.cpp - OrthancServer/OrthancMoveRequestHandler.cpp - OrthancServer/OrthancPeerParameters.cpp - OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp - OrthancServer/OrthancRestApi/OrthancRestApi.cpp - OrthancServer/OrthancRestApi/OrthancRestArchive.cpp - OrthancServer/OrthancRestApi/OrthancRestChanges.cpp - OrthancServer/OrthancRestApi/OrthancRestModalities.cpp - OrthancServer/OrthancRestApi/OrthancRestResources.cpp - OrthancServer/OrthancRestApi/OrthancRestSystem.cpp - OrthancServer/ParsedDicomFile.cpp - OrthancServer/QueryRetrieveHandler.cpp - OrthancServer/Search/LookupIdentifierQuery.cpp - OrthancServer/Search/LookupResource.cpp - OrthancServer/Search/SetOfResources.cpp - OrthancServer/Search/ListConstraint.cpp - OrthancServer/Search/RangeConstraint.cpp - OrthancServer/Search/ValueConstraint.cpp - OrthancServer/Search/WildcardConstraint.cpp - OrthancServer/ServerContext.cpp - OrthancServer/ServerEnumerations.cpp - OrthancServer/ServerIndex.cpp - OrthancServer/ServerToolbox.cpp - OrthancServer/SliceOrdering.cpp - OrthancServer/ToDcmtkBridge.cpp - - # From "lua-scripting" branch - OrthancServer/DicomInstanceToStore.cpp - OrthancServer/Scheduler/DeleteInstanceCommand.cpp - OrthancServer/Scheduler/ModifyInstanceCommand.cpp - OrthancServer/Scheduler/ServerCommandInstance.cpp - OrthancServer/Scheduler/ServerJob.cpp - OrthancServer/Scheduler/ServerScheduler.cpp - OrthancServer/Scheduler/StorePeerCommand.cpp - OrthancServer/Scheduler/StoreScuCommand.cpp - OrthancServer/Scheduler/CallSystemCommand.cpp - ) - - -set(ORTHANC_UNIT_TESTS_SOURCES - UnitTestsSources/DicomMapTests.cpp - UnitTestsSources/FileStorageTests.cpp - UnitTestsSources/FromDcmtkTests.cpp - UnitTestsSources/MemoryCacheTests.cpp - UnitTestsSources/ImageTests.cpp - UnitTestsSources/RestApiTests.cpp - UnitTestsSources/SQLiteTests.cpp - UnitTestsSources/SQLiteChromiumTests.cpp - UnitTestsSources/ServerIndexTests.cpp - UnitTestsSources/VersionsTests.cpp - UnitTestsSources/ZipTests.cpp - UnitTestsSources/LuaTests.cpp - UnitTestsSources/MultiThreadingTests.cpp - UnitTestsSources/UnitTestsMain.cpp - UnitTestsSources/ImageProcessingTests.cpp - UnitTestsSources/JpegLosslessTests.cpp - UnitTestsSources/StreamTests.cpp - ) - - -if (ENABLE_PLUGINS) - list(APPEND ORTHANC_SERVER_SOURCES - Plugins/Engine/OrthancPluginDatabase.cpp - Plugins/Engine/OrthancPlugins.cpp - Plugins/Engine/PluginsEnumerations.cpp - Plugins/Engine/PluginsErrorDictionary.cpp - Plugins/Engine/PluginsManager.cpp - Plugins/Engine/SharedLibrary.cpp - ) - - list(APPEND ORTHANC_UNIT_TESTS_SOURCES - UnitTestsSources/PluginsTests.cpp - ) -endif() - - -set(ORTHANC_EMBEDDED_FILES - PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql - UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql - UPGRADE_DATABASE_4_TO_5 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade4To5.sql - CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json - DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt - LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua - FONT_UBUNTU_MONO_BOLD_16 ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/UbuntuMonoBold-16.json - ) - - - -##################################################################### -## Inclusion of third-party dependencies -##################################################################### - -if (ENABLE_GOOGLE_LOG) - include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.cmake) -endif() - -include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibJpegConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/PugixmlConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake) - -# These are the two most heavyweight dependencies. We put them as the -# last includes to quickly spot problems when configuring static -# builds. -include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake) - - -if (ENABLE_SSL) - add_definitions(-DORTHANC_SSL_ENABLED=1) - include(${CMAKE_SOURCE_DIR}/Resources/CMake/OpenSslConfiguration.cmake) -else() - add_definitions(-DORTHANC_SSL_ENABLED=0) -endif() - - -if (ENABLE_JPEG) - add_definitions(-DORTHANC_JPEG_ENABLED=1) -else() - add_definitions(-DORTHANC_JPEG_ENABLED=0) -endif() - - -if (ENABLE_JPEG_LOSSLESS) - add_definitions(-DORTHANC_JPEG_LOSSLESS_ENABLED=1) -else() - add_definitions(-DORTHANC_JPEG_LOSSLESS_ENABLED=0) -endif() - - -if (ENABLE_PLUGINS) - add_definitions(-DORTHANC_PLUGINS_ENABLED=1) -else() - add_definitions(-DORTHANC_PLUGINS_ENABLED=0) -endif() - - - -##################################################################### -## Autogeneration of files -##################################################################### - -if (STANDALONE_BUILD) - # We embed all the resources in the binaries for standalone builds - add_definitions(-DORTHANC_STANDALONE=1) - EmbedResources( - ${ORTHANC_EMBEDDED_FILES} - ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer - ${DCMTK_DICTIONARIES} - ) -else() - add_definitions( - -DORTHANC_STANDALONE=0 - -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\" - ) - EmbedResources( - ${ORTHANC_EMBEDDED_FILES} - ) -endif() - -if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py - ${ORTHANC_VERSION} Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/Orthanc.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - list(APPEND ORTHANC_RESOURCES ${AUTOGENERATED_DIR}/Orthanc.rc) -endif() - - - -##################################################################### -## Build the core of Orthanc -##################################################################### - -# Setup precompiled headers for Microsoft Visual Studio -if (MSVC) - add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1) - - ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( - "PrecompiledHeaders.h" "Core/PrecompiledHeaders.cpp" ORTHANC_CORE_SOURCES) - - ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( - "PrecompiledHeadersServer.h" "OrthancServer/PrecompiledHeadersServer.cpp" ORTHANC_SERVER_SOURCES) - - ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( - "PrecompiledHeadersUnitTests.h" "UnitTestsSources/PrecompiledHeadersUnitTests.cpp" ORTHANC_UNIT_TESTS_SOURCES) -endif() - - -add_definitions( - -DORTHANC_VERSION="${ORTHANC_VERSION}" - -DORTHANC_DATABASE_VERSION=${ORTHANC_DATABASE_VERSION} - -DORTHANC_ENABLE_LOGGING=1 - ) - -list(LENGTH OPENSSL_SOURCES OPENSSL_SOURCES_LENGTH) -if (${OPENSSL_SOURCES_LENGTH} GREATER 0) - add_library(OpenSSL STATIC ${OPENSSL_SOURCES}) -endif() - -add_library(CoreLibrary - STATIC - ${ORTHANC_CORE_SOURCES} - ${AUTOGENERATED_SOURCES} - - ${BOOST_SOURCES} - ${CURL_SOURCES} - ${GOOGLE_LOG_SOURCES} - ${JSONCPP_SOURCES} - ${LIBPNG_SOURCES} - ${LIBJPEG_SOURCES} - ${LUA_SOURCES} - ${MONGOOSE_SOURCES} - ${PUGIXML_SOURCES} - ${SQLITE_SOURCES} - ${ZLIB_SOURCES} - - ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/md5/md5.c - ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/base64/base64.cpp - - # This is the minizip distribution to create ZIP files using zlib - ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c - ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/zip.c - ) - - - -##################################################################### -## Build the Orthanc server -##################################################################### - -add_library(ServerLibrary - STATIC - ${DCMTK_SOURCES} - ${ORTHANC_SERVER_SOURCES} - ) - -# Ensure autogenerated code is built before building ServerLibrary -add_dependencies(ServerLibrary CoreLibrary) - -add_executable(Orthanc - OrthancServer/main.cpp - ${ORTHANC_RESOURCES} - ) - -target_link_libraries(Orthanc ServerLibrary CoreLibrary ${DCMTK_LIBRARIES}) - -if (${OPENSSL_SOURCES_LENGTH} GREATER 0) - target_link_libraries(Orthanc OpenSSL) -endif() - -install( - TARGETS Orthanc - RUNTIME DESTINATION sbin - ) - - - -##################################################################### -## Build the unit tests -##################################################################### - -if (UNIT_TESTS_WITH_HTTP_CONNEXIONS) - add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1) -else() - add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0) -endif() - -add_definitions( - -DORTHANC_BUILD_UNIT_TESTS=1 - ) - -include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) -add_executable(UnitTests - ${GTEST_SOURCES} - ${ORTHANC_UNIT_TESTS_SOURCES} - ) -target_link_libraries(UnitTests ServerLibrary CoreLibrary ${DCMTK_LIBRARIES}) - -if (${OPENSSL_SOURCES_LENGTH} GREATER 0) - target_link_libraries(UnitTests OpenSSL) -endif() - - - -##################################################################### -## Build the "ServeFolders" plugin -##################################################################### - -if (ENABLE_PLUGINS AND BUILD_SERVE_FOLDERS) - execute_process( - COMMAND - ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py - ${ORTHANC_VERSION} ServeFolders ServeFolders.dll "Orthanc plugin to serve additional folders" - ERROR_VARIABLE Failure - OUTPUT_FILE ${AUTOGENERATED_DIR}/ServeFolders.rc - ) - - if (Failure) - message(FATAL_ERROR "Error while computing the version information: ${Failure}") - endif() - - add_definitions(-DSERVE_FOLDERS_VERSION="${ORTHANC_VERSION}") - - include_directories(${CMAKE_SOURCE_DIR}/Plugins/Include) - - add_library(ServeFolders SHARED - ${BOOST_SOURCES} - ${JSONCPP_SOURCES} - Plugins/Samples/ServeFolders/Plugin.cpp - ${AUTOGENERATED_DIR}/ServeFolders.rc - ) - - set_target_properties( - ServeFolders PROPERTIES - VERSION ${ORTHANC_VERSION} - SOVERSION ${ORTHANC_VERSION} - ) - - install( - TARGETS ServeFolders - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux - ) -endif() - - - -##################################################################### -## Generate the documentation if Doxygen is present -##################################################################### - -find_package(Doxygen) -if (DOXYGEN_FOUND) - configure_file( - ${CMAKE_SOURCE_DIR}/Resources/Orthanc.doxygen - ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen - @ONLY) - - configure_file( - ${CMAKE_SOURCE_DIR}/Resources/OrthancPlugin.doxygen - ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen - @ONLY) - - add_custom_target(doc - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen - COMMENT "Generating internal documentation with Doxygen" VERBATIM - ) - - add_custom_command(TARGET Orthanc - POST_BUILD - COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating plugin documentation with Doxygen" VERBATIM - ) - - install( - DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancPluginDocumentation/doc/ - DESTINATION share/doc/orthanc/OrthancPlugin - ) -else() - message("Doxygen not found. The documentation will not be built.") -endif() - - - -##################################################################### -## Install the plugin SDK -##################################################################### - -if (ENABLE_PLUGINS) - install( - FILES - Plugins/Include/orthanc/OrthancCPlugin.h - Plugins/Include/orthanc/OrthancCDatabasePlugin.h - Plugins/Include/orthanc/OrthancCppDatabasePlugin.h - DESTINATION include/orthanc - ) -endif() - - - -##################################################################### -## Prepare the "uninstall" target -## http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F -##################################################################### - -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/Resources/CMake/Uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE @ONLY) - -add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +cmake_minimum_required(VERSION 2.8) + +project(Orthanc) + + +##################################################################### +## Generic parameters of the Orthanc framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/Resources/CMake/OrthancFrameworkParameters.cmake) + +# Enable all the optional components of the Orthanc framework +set(ENABLE_CRYPTO_OPTIONS ON) +set(ENABLE_DCMTK ON) +set(ENABLE_DCMTK_NETWORKING ON) +set(ENABLE_GOOGLE_TEST ON) +set(ENABLE_JPEG ON) +set(ENABLE_LOCALE ON) +set(ENABLE_LUA ON) +set(ENABLE_PNG ON) +set(ENABLE_PUGIXML ON) +set(ENABLE_SQLITE ON) +set(ENABLE_WEB_CLIENT ON) +set(ENABLE_WEB_SERVER ON) +set(ENABLE_ZLIB ON) + +set(HAS_EMBEDDED_RESOURCES ON) + + +##################################################################### +## CMake parameters tunable at the command line to configure the +## plugins, the companion tools, and the unit tests +##################################################################### + +# Parameters of the build +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(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") +SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") + + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/OrthancFrameworkConfiguration.cmake) + + +##################################################################### +## List of source files +##################################################################### + +set(ORTHANC_SERVER_SOURCES + OrthancServer/DatabaseWrapper.cpp + OrthancServer/DicomInstanceOrigin.cpp + OrthancServer/DicomInstanceToStore.cpp + OrthancServer/ExportedResource.cpp + OrthancServer/LuaScripting.cpp + OrthancServer/OrthancFindRequestHandler.cpp + OrthancServer/OrthancHttpHandler.cpp + OrthancServer/OrthancInitialization.cpp + OrthancServer/OrthancMoveRequestHandler.cpp + OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp + OrthancServer/OrthancRestApi/OrthancRestApi.cpp + OrthancServer/OrthancRestApi/OrthancRestArchive.cpp + OrthancServer/OrthancRestApi/OrthancRestChanges.cpp + OrthancServer/OrthancRestApi/OrthancRestModalities.cpp + OrthancServer/OrthancRestApi/OrthancRestResources.cpp + OrthancServer/OrthancRestApi/OrthancRestSystem.cpp + OrthancServer/QueryRetrieveHandler.cpp + OrthancServer/Search/HierarchicalMatcher.cpp + OrthancServer/Search/IFindConstraint.cpp + OrthancServer/Search/ListConstraint.cpp + OrthancServer/Search/LookupIdentifierQuery.cpp + OrthancServer/Search/LookupResource.cpp + OrthancServer/Search/RangeConstraint.cpp + OrthancServer/Search/SetOfResources.cpp + OrthancServer/Search/ValueConstraint.cpp + OrthancServer/Search/WildcardConstraint.cpp + OrthancServer/ServerContext.cpp + OrthancServer/ServerEnumerations.cpp + OrthancServer/ServerIndex.cpp + OrthancServer/ServerJobs/ArchiveJob.cpp + OrthancServer/ServerJobs/DicomModalityStoreJob.cpp + OrthancServer/ServerJobs/DicomMoveScuJob.cpp + OrthancServer/ServerJobs/LuaJobManager.cpp + OrthancServer/ServerJobs/MergeStudyJob.cpp + OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp + OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp + OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp + OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp + OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp + OrthancServer/ServerJobs/OrthancJobUnserializer.cpp + OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp + OrthancServer/ServerJobs/ResourceModificationJob.cpp + OrthancServer/ServerJobs/SplitStudyJob.cpp + OrthancServer/ServerToolbox.cpp + OrthancServer/SliceOrdering.cpp + ) + + +set(ORTHANC_UNIT_TESTS_SOURCES + UnitTestsSources/DicomMapTests.cpp + UnitTestsSources/FileStorageTests.cpp + UnitTestsSources/FromDcmtkTests.cpp + UnitTestsSources/MemoryCacheTests.cpp + UnitTestsSources/ImageTests.cpp + UnitTestsSources/RestApiTests.cpp + UnitTestsSources/SQLiteTests.cpp + UnitTestsSources/SQLiteChromiumTests.cpp + UnitTestsSources/ServerIndexTests.cpp + UnitTestsSources/VersionsTests.cpp + UnitTestsSources/ZipTests.cpp + UnitTestsSources/LuaTests.cpp + UnitTestsSources/MultiThreadingTests.cpp + UnitTestsSources/UnitTestsMain.cpp + UnitTestsSources/ImageProcessingTests.cpp + UnitTestsSources/JpegLosslessTests.cpp + UnitTestsSources/StreamTests.cpp + ) + + +if (ENABLE_PLUGINS) + list(APPEND ORTHANC_SERVER_SOURCES + Plugins/Engine/OrthancPluginDatabase.cpp + Plugins/Engine/OrthancPlugins.cpp + Plugins/Engine/PluginsEnumerations.cpp + Plugins/Engine/PluginsErrorDictionary.cpp + Plugins/Engine/PluginsJob.cpp + Plugins/Engine/PluginsManager.cpp + ) + + list(APPEND ORTHANC_UNIT_TESTS_SOURCES + UnitTestsSources/PluginsTests.cpp + ) +endif() + + +if (CMAKE_COMPILER_IS_GNUCXX + AND NOT CMAKE_CROSSCOMPILING + AND USE_DCMTK_360) + # Add the "-pedantic" flag only on the Orthanc sources, and only if + # cross-compiling DCMTK 3.6.0 + set(ORTHANC_ALL_SOURCES + ${ORTHANC_CORE_SOURCES_INTERNAL} + ${ORTHANC_DICOM_SOURCES_INTERNAL} + ${ORTHANC_SERVER_SOURCES} + ${ORTHANC_UNIT_TESTS_SOURCES} + Plugins/Samples/ServeFolders/Plugin.cpp + Plugins/Samples/ModalityWorklists/Plugin.cpp + OrthancServer/main.cpp + ) + + set_source_files_properties(${ORTHANC_ALL_SOURCES} + PROPERTIES COMPILE_FLAGS -pedantic + ) +endif() + + +##################################################################### +## Autogeneration of files +##################################################################### + +set(ORTHANC_EMBEDDED_FILES + PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql + UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql + UPGRADE_DATABASE_4_TO_5 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade4To5.sql + CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json + DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt + LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua + FONT_UBUNTU_MONO_BOLD_16 ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/UbuntuMonoBold-16.json + ) + +if (STANDALONE_BUILD) + # We embed all the resources in the binaries for standalone builds + add_definitions(-DORTHANC_STANDALONE=1) + EmbedResources( + ${ORTHANC_EMBEDDED_FILES} + ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer + ${DCMTK_DICTIONARIES} + ) +else() + add_definitions( + -DORTHANC_STANDALONE=0 + -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\" + ) + EmbedResources( + ${ORTHANC_EMBEDDED_FILES} + ) +endif() + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/Orthanc.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND ORTHANC_RESOURCES ${AUTOGENERATED_DIR}/Orthanc.rc) +endif() + + + +##################################################################### +## Configuration of the C/C++ macros +##################################################################### + +if (ENABLE_PLUGINS) + add_definitions(-DORTHANC_ENABLE_PLUGINS=1) +else() + add_definitions(-DORTHANC_ENABLE_PLUGINS=0) +endif() + + +if (UNIT_TESTS_WITH_HTTP_CONNEXIONS) + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1) +else() + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0) +endif() + + +include_directories(${CMAKE_SOURCE_DIR}/Plugins/Include) + +add_definitions( + -DORTHANC_BUILD_UNIT_TESTS=1 + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + + # Macros for the plugins + -DHAS_ORTHANC_EXCEPTION=0 + -DMODALITY_WORKLISTS_VERSION="${ORTHANC_VERSION}" + -DSERVE_FOLDERS_VERSION="${ORTHANC_VERSION}" + ) + + +# Setup precompiled headers for Microsoft Visual Studio + +# WARNING: There must be NO MORE "add_definitions()", "include()" or +# "include_directories()" below, otherwise the generated precompiled +# headers might get broken! + +if (MSVC) + add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1) + + set(TMP + ${ORTHANC_CORE_SOURCES_INTERNAL} + ${ORTHANC_DICOM_SOURCES_INTERNAL} + ) + + ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( + "PrecompiledHeaders.h" "Core/PrecompiledHeaders.cpp" + TMP ORTHANC_CORE_PCH) + + ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( + "PrecompiledHeadersServer.h" "OrthancServer/PrecompiledHeadersServer.cpp" + ORTHANC_SERVER_SOURCES ORTHANC_SERVER_PCH) + + ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( + "PrecompiledHeadersUnitTests.h" "UnitTestsSources/PrecompiledHeadersUnitTests.cpp" + ORTHANC_UNIT_TESTS_SOURCES ORTHANC_UNIT_TESTS_PCH) +endif() + + + +##################################################################### +## Build the core of Orthanc +##################################################################### + +# "CoreLibrary" contains all the third-party dependencies and the +# content of the "Core" folder +add_library(CoreLibrary + STATIC + ${ORTHANC_CORE_PCH} + ${ORTHANC_CORE_SOURCES} + ${ORTHANC_DICOM_SOURCES} + ${AUTOGENERATED_SOURCES} + ) + + +##################################################################### +## Build the Orthanc server +##################################################################### + +add_library(ServerLibrary + STATIC + ${ORTHANC_SERVER_PCH} + ${ORTHANC_SERVER_SOURCES} + ) + +# Ensure autogenerated code is built before building ServerLibrary +add_dependencies(ServerLibrary CoreLibrary) + +add_executable(Orthanc + OrthancServer/main.cpp + ${ORTHANC_RESOURCES} + ) + +target_link_libraries(Orthanc ServerLibrary CoreLibrary ${DCMTK_LIBRARIES}) + +install( + TARGETS Orthanc + RUNTIME DESTINATION sbin + ) + + +##################################################################### +## Build the unit tests +##################################################################### + +add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_UNIT_TESTS_PCH} + ${ORTHANC_UNIT_TESTS_SOURCES} + ) + +target_link_libraries(UnitTests + ServerLibrary + CoreLibrary + ${DCMTK_LIBRARIES} + ${GOOGLE_TEST_LIBRARIES} + ) + + +##################################################################### +## Build the "ServeFolders" plugin +##################################################################### + +if (ENABLE_PLUGINS AND BUILD_SERVE_FOLDERS) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} ServeFolders ServeFolders.dll "Orthanc plugin to serve additional folders" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/ServeFolders.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND SERVE_FOLDERS_RESOURCES ${AUTOGENERATED_DIR}/ServeFolders.rc) + endif() + + add_library(ServeFolders SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${LIBICONV_SOURCES} + Plugins/Samples/ServeFolders/Plugin.cpp + Plugins/Samples/Common/OrthancPluginCppWrapper.cpp + ${SERVE_FOLDERS_RESOURCES} + ) + + set_target_properties( + ServeFolders PROPERTIES + VERSION ${ORTHANC_VERSION} + SOVERSION ${ORTHANC_VERSION} + ) + + install( + TARGETS ServeFolders + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) +endif() + + + +##################################################################### +## Build the "ModalityWorklists" plugin +##################################################################### + +if (ENABLE_PLUGINS AND BUILD_MODALITY_WORKLISTS) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} ModalityWorklists ModalityWorklists.dll "Sample Orthanc plugin to serve modality worklists" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/ModalityWorklists.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND MODALITY_WORKLISTS_RESOURCES ${AUTOGENERATED_DIR}/ModalityWorklists.rc) + endif() + + add_library(ModalityWorklists SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${LIBICONV_SOURCES} + Plugins/Samples/Common/OrthancPluginCppWrapper.cpp + Plugins/Samples/ModalityWorklists/Plugin.cpp + ${MODALITY_WORKLISTS_RESOURCES} + ) + + set_target_properties( + ModalityWorklists PROPERTIES + VERSION ${ORTHANC_VERSION} + SOVERSION ${ORTHANC_VERSION} + ) + + install( + TARGETS ModalityWorklists + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) +endif() + + + +##################################################################### +## Build the companion tool to recover files compressed using Orthanc +##################################################################### + +if (BUILD_RECOVER_COMPRESSED_FILE) + set(RECOVER_COMPRESSED_SOURCES + Resources/Samples/Tools/RecoverCompressedFile.cpp + ) + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} OrthancRecoverCompressedFile OrthancRecoverCompressedFile.exe + "Lightweight, RESTful DICOM server for medical imaging" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/OrthancRecoverCompressedFile.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND RECOVER_COMPRESSED_SOURCES + ${AUTOGENERATED_DIR}/OrthancRecoverCompressedFile.rc + ) + endif() + + add_executable(OrthancRecoverCompressedFile ${RECOVER_COMPRESSED_SOURCES}) + + target_link_libraries(OrthancRecoverCompressedFile CoreLibrary) + + install( + TARGETS OrthancRecoverCompressedFile + RUNTIME DESTINATION bin + ) +endif() + + + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/Orthanc.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen + @ONLY) + + configure_file( + ${CMAKE_SOURCE_DIR}/Resources/OrthancPlugin.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen + COMMENT "Generating internal documentation with Doxygen" VERBATIM + ) + + add_custom_command(TARGET Orthanc + POST_BUILD + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating plugin documentation with Doxygen" VERBATIM + ) + + install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancPluginDocumentation/doc/ + DESTINATION share/doc/orthanc/OrthancPlugin + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif() + + + +##################################################################### +## Install the plugin SDK +##################################################################### + +if (ENABLE_PLUGINS) + install( + FILES + Plugins/Include/orthanc/OrthancCPlugin.h + Plugins/Include/orthanc/OrthancCDatabasePlugin.h + DESTINATION include/orthanc + ) +endif() + + + +##################################################################### +## Prepare the "uninstall" target +## http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F +##################################################################### + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/Resources/CMake/Uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
--- a/Core/Cache/ICachePageProvider.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Cache/ICachePageProvider.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/LeastRecentlyUsedIndex.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Cache/LeastRecentlyUsedIndex.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Cache/MemoryCache.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -77,6 +78,18 @@ { } + 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() { while (!index_.IsEmpty())
--- a/Core/Cache/MemoryCache.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Cache/MemoryCache.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,5 +64,7 @@ ~MemoryCache(); IDynamicObject& Access(const std::string& id); + + void Invalidate(const std::string& id); }; }
--- a/Core/Cache/SharedArchive.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Cache/SharedArchive.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ #include "../PrecompiledHeaders.h" #include "SharedArchive.h" - -#include "../Uuid.h" +#include "../Toolbox.h" namespace Orthanc
--- a/Core/Cache/SharedArchive.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Cache/SharedArchive.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +33,14 @@ #pragma once +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The class SharedArchive cannot be used in sandboxed environments +#endif + #include "LeastRecentlyUsedIndex.h" #include "../IDynamicObject.h"
--- a/Core/ChunkedBuffer.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/ChunkedBuffer.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,17 +52,19 @@ } - void ChunkedBuffer::AddChunk(const char* chunkData, + void ChunkedBuffer::AddChunk(const void* chunkData, size_t chunkSize) { if (chunkSize == 0) { return; } - - assert(chunkData != NULL); - chunks_.push_back(new std::string(chunkData, chunkSize)); - numBytes_ += chunkSize; + else + { + assert(chunkData != NULL); + chunks_.push_back(new std::string(reinterpret_cast<const char*>(chunkData), chunkSize)); + numBytes_ += chunkSize; + } } @@ -95,5 +98,6 @@ } chunks_.clear(); + numBytes_ = 0; } }
--- a/Core/ChunkedBuffer.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/ChunkedBuffer.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +62,7 @@ return numBytes_; } - void AddChunk(const char* chunkData, + void AddChunk(const void* chunkData, size_t chunkSize); void AddChunk(const std::string& chunk);
--- a/Core/Compression/DeflateBaseCompressor.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/DeflateBaseCompressor.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/DeflateBaseCompressor.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,15 @@ #include "IBufferCompressor.h" +#if !defined(ORTHANC_ENABLE_ZLIB) +# error The macro ORTHANC_ENABLE_ZLIB must be defined +#endif + +#if ORTHANC_ENABLE_ZLIB != 1 +# error ZLIB support must be enabled to include this file +#endif + + #include <stdint.h> namespace Orthanc
--- a/Core/Compression/GzipCompressor.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/GzipCompressor.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/GzipCompressor.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/HierarchicalZipWriter.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/HierarchicalZipWriter.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,6 +39,10 @@ #include <list> #include <boost/lexical_cast.hpp> +#if ORTHANC_BUILD_UNIT_TESTS == 1 +# include <gtest/gtest_prod.h> +#endif + namespace Orthanc { class HierarchicalZipWriter
--- a/Core/Compression/IBufferCompressor.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/IBufferCompressor.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/ZipWriter.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/ZipWriter.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,14 +33,19 @@ #pragma once +#if !defined(ORTHANC_ENABLE_ZLIB) +# error The macro ORTHANC_ENABLE_ZLIB must be defined +#endif + +#if ORTHANC_ENABLE_ZLIB != 1 +# error ZLIB support must be enabled to include this file +#endif + + #include <stdint.h> #include <string> #include <boost/shared_ptr.hpp> -#if ORTHANC_BUILD_UNIT_TESTS == 1 -#include <gtest/gtest_prod.h> -#endif - namespace Orthanc { class ZipWriter
--- a/Core/Compression/ZlibCompressor.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/ZlibCompressor.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Compression/ZlibCompressor.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomArray.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomArray.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +48,7 @@ Elements elements_; public: - DicomArray(const DicomMap& map); + explicit DicomArray(const DicomMap& map); ~DicomArray();
--- a/Core/DicomFormat/DicomElement.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomElement.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomImageInformation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -44,6 +45,7 @@ #include <limits> #include <cassert> #include <stdio.h> +#include <memory> namespace Orthanc { @@ -157,7 +159,7 @@ if (samplesPerPixel_ > 1) { // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1 - // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/ + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3 try { planarConfiguration = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).GetContent()); @@ -177,18 +179,20 @@ throw OrthancException(ErrorCode_NotImplemented); } - try - { - numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent()); - } - catch (OrthancException&) + if (values.HasTag(DICOM_TAG_NUMBER_OF_FRAMES)) { - // If the tag "NumberOfFrames" is absent, assume there is a single frame - numberOfFrames_ = 1; + try + { + numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent()); + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_NotImplemented); + } } - catch (boost::bad_lexical_cast&) + else { - throw OrthancException(ErrorCode_NotImplemented); + numberOfFrames_ = 1; } if ((bitsAllocated_ != 8 && bitsAllocated_ != 16 && @@ -199,13 +203,6 @@ throw OrthancException(ErrorCode_NotImplemented); } - if (bitsAllocated_ > 32 || - bitsStored_ >= 32) - { - // Not available, as the accessor internally uses int32_t values - throw OrthancException(ErrorCode_NotImplemented); - } - if (samplesPerPixel_ == 0) { throw OrthancException(ErrorCode_NotImplemented); @@ -217,10 +214,44 @@ isSigned_ = (pixelRepresentation != 0 ? true : false); } + DicomImageInformation* DicomImageInformation::Clone() const + { + std::auto_ptr<DicomImageInformation> target(new DicomImageInformation); + target->width_ = width_; + target->height_ = height_; + target->samplesPerPixel_ = samplesPerPixel_; + target->numberOfFrames_ = numberOfFrames_; + target->isPlanar_ = isPlanar_; + target->isSigned_ = isSigned_; + target->bytesPerValue_ = bytesPerValue_; + target->bitsAllocated_ = bitsAllocated_; + target->bitsStored_ = bitsStored_; + target->highBit_ = highBit_; + target->photometric_ = photometric_; - bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format) const + return target.release(); + } + + bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format, + bool ignorePhotometricInterpretation) const { - if (photometric_ == PhotometricInterpretation_Monochrome1 || + if (photometric_ == PhotometricInterpretation_Palette) + { + if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_RGB24; + return true; + } + + if (GetBitsStored() == 16 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_RGB48; + return true; + } + } + + if (ignorePhotometricInterpretation || + photometric_ == PhotometricInterpretation_Monochrome1 || photometric_ == PhotometricInterpretation_Monochrome2) { if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) @@ -240,12 +271,18 @@ format = PixelFormat_SignedGrayscale16; return true; } + + if (GetBitsAllocated() == 32 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_Grayscale32; + return true; + } } if (GetBitsStored() == 8 && GetChannelCount() == 3 && !IsSigned() && - photometric_ == PhotometricInterpretation_RGB) + (ignorePhotometricInterpretation || photometric_ == PhotometricInterpretation_RGB)) { format = PixelFormat_RGB24; return true; @@ -253,4 +290,13 @@ return false; } + + + size_t DicomImageInformation::GetFrameSize() const + { + return (GetHeight() * + GetWidth() * + GetBytesPerValue() * + GetChannelCount()); + } }
--- a/Core/DicomFormat/DicomImageInformation.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomImageInformation.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,8 +57,15 @@ PhotometricInterpretation photometric_; + protected: + explicit DicomImageInformation() + { + } + public: - DicomImageInformation(const DicomMap& values); + explicit DicomImageInformation(const DicomMap& values); + + DicomImageInformation* Clone() const; unsigned int GetWidth() const { @@ -119,6 +127,9 @@ return photometric_; } - bool ExtractPixelFormat(PixelFormat& format) const; + bool ExtractPixelFormat(PixelFormat& format, + bool ignorePhotometricInterpretation) const; + + size_t GetFrameSize() const; }; }
--- a/Core/DicomFormat/DicomInstanceHasher.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomInstanceHasher.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +54,15 @@ pixelData_(pixelData), size_(size) { + if (information_.GetBitsAllocated() > 32 || + information_.GetBitsStored() >= 32) + { + // Not available, as the accessor internally uses int32_t values + throw OrthancException(ErrorCode_NotImplemented); + } + frame_ = 0; - frameOffset_ = (information_.GetHeight() * information_.GetWidth() * - information_.GetBytesPerValue() * information_.GetChannelCount()); + frameOffset_ = information_.GetFrameSize(); if (information_.GetNumberOfFrames() * frameOffset_ > size) { @@ -137,7 +144,7 @@ const uint8_t* pixel = reinterpret_cast<const uint8_t*>(pixelData_) + y * rowOffset_ + frame_ * frameOffset_; - // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/ + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3 if (information_.IsPlanar()) { /**
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomMap.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +36,9 @@ #include <stdio.h> #include <memory> -#include "DicomArray.h" + +#include "../Endianness.h" +#include "../Logging.h" #include "../OrthancException.h" @@ -61,7 +64,11 @@ DicomTag(0x0020, 0x0010), // StudyID DICOM_TAG_STUDY_DESCRIPTION, DICOM_TAG_ACCESSION_NUMBER, - DICOM_TAG_STUDY_INSTANCE_UID + 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 }; static DicomTag seriesTags[] = @@ -83,7 +90,12 @@ 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_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 }; static DicomTag instanceTags[] = @@ -96,7 +108,17 @@ 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_POSITION_PATIENT, // New in db v6 + DICOM_TAG_IMAGE_COMMENTS, // New in db v6 + + /** + * Main DICOM tags that are not part of any release of the + * database schema yet, and that will be part of future db v7. In + * the meantime, the user must call "/tools/reconstruct" once to + * 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 }; @@ -157,12 +179,11 @@ } - - void DicomMap::Clear() { for (Map::iterator it = map_.begin(); it != map_.end(); ++it) { + assert(it->second != NULL); delete it->second; } @@ -282,7 +303,7 @@ for (size_t i = 0; i < count; i++) { - result.SetValue(tags[i], ""); + result.SetValue(tags[i], "", false); } } @@ -294,16 +315,22 @@ void DicomMap::SetupFindStudyTemplate(DicomMap& result) { SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag)); - result.SetValue(DICOM_TAG_ACCESSION_NUMBER, ""); - result.SetValue(DICOM_TAG_PATIENT_ID, ""); + result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); + result.SetValue(DICOM_TAG_PATIENT_ID, "", false); + + // These main DICOM tags are only indirectly related to the + // General Study Module, remove them + result.Remove(DICOM_TAG_INSTITUTION_NAME); + result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN); + result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION); } void DicomMap::SetupFindSeriesTemplate(DicomMap& result) { SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag)); - result.SetValue(DICOM_TAG_ACCESSION_NUMBER, ""); - result.SetValue(DICOM_TAG_PATIENT_ID, ""); - result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, ""); + result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); + result.SetValue(DICOM_TAG_PATIENT_ID, "", false); + result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); // These tags are considered as "main" by Orthanc, but are not in the Series module result.Remove(DicomTag(0x0008, 0x0070)); // Manufacturer @@ -315,15 +342,18 @@ result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS); result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES); result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT); + result.Remove(DICOM_TAG_SERIES_TYPE); + result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION); + result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT); } void DicomMap::SetupFindInstanceTemplate(DicomMap& result) { SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag)); - result.SetValue(DICOM_TAG_ACCESSION_NUMBER, ""); - result.SetValue(DICOM_TAG_PATIENT_ID, ""); - result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, ""); - result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, ""); + result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); + result.SetValue(DICOM_TAG_PATIENT_ID, "", false); + result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); + result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false); } @@ -443,13 +473,6 @@ } - void DicomMap::Print(FILE* fp) const - { - DicomArray a(*this); - a.Print(fp); - } - - void DicomMap::GetTags(std::set<DicomTag>& tags) const { tags.clear(); @@ -460,4 +483,548 @@ tags.insert(it->first); } } + + + static uint16_t ReadUnsignedInteger16(const char* dicom) + { + return le16toh(*reinterpret_cast<const uint16_t*>(dicom)); + } + + + static uint32_t ReadUnsignedInteger32(const char* dicom) + { + return le32toh(*reinterpret_cast<const uint32_t*>(dicom)); + } + + + static bool ValidateTag(const ValueRepresentation& vr, + const std::string& value) + { + switch (vr) + { + case ValueRepresentation_ApplicationEntity: + return value.size() <= 16; + + case ValueRepresentation_AgeString: + return (value.size() == 4 && + isdigit(value[0]) && + isdigit(value[1]) && + isdigit(value[2]) && + (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y')); + + case ValueRepresentation_AttributeTag: + return value.size() == 4; + + case ValueRepresentation_CodeString: + return value.size() <= 16; + + case ValueRepresentation_Date: + return value.size() <= 18; + + case ValueRepresentation_DecimalString: + return value.size() <= 16; + + case ValueRepresentation_DateTime: + return value.size() <= 54; + + case ValueRepresentation_FloatingPointSingle: + return value.size() == 4; + + case ValueRepresentation_FloatingPointDouble: + return value.size() == 8; + + case ValueRepresentation_IntegerString: + return value.size() <= 12; + + case ValueRepresentation_LongString: + return value.size() <= 64; + + case ValueRepresentation_LongText: + return value.size() <= 10240; + + case ValueRepresentation_OtherByte: + return true; + + case ValueRepresentation_OtherDouble: + return value.size() <= (static_cast<uint64_t>(1) << 32) - 8; + + case ValueRepresentation_OtherFloat: + return value.size() <= (static_cast<uint64_t>(1) << 32) - 4; + + case ValueRepresentation_OtherLong: + return true; + + case ValueRepresentation_OtherWord: + return true; + + case ValueRepresentation_PersonName: + return true; + + case ValueRepresentation_ShortString: + return value.size() <= 16; + + case ValueRepresentation_SignedLong: + return value.size() == 4; + + case ValueRepresentation_Sequence: + return true; + + case ValueRepresentation_SignedShort: + return value.size() == 2; + + case ValueRepresentation_ShortText: + return value.size() <= 1024; + + case ValueRepresentation_Time: + return value.size() <= 28; + + case ValueRepresentation_UnlimitedCharacters: + return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; + + case ValueRepresentation_UniqueIdentifier: + return value.size() <= 64; + + case ValueRepresentation_UnsignedLong: + return value.size() == 4; + + case ValueRepresentation_Unknown: + return true; + + case ValueRepresentation_UniversalResource: + return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; + + case ValueRepresentation_UnsignedShort: + return value.size() == 2; + + case ValueRepresentation_UnlimitedText: + return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; + + default: + // Assume unsupported tags are OK + return true; + } + } + + + static void RemoveTagPadding(std::string& value, + const ValueRepresentation& vr) + { + /** + * Remove padding from character strings, if need be. For the time + * being, only the UI VR is supported. + * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html + **/ + + switch (vr) + { + case ValueRepresentation_UniqueIdentifier: + { + /** + * "Values with a VR of UI shall be padded with a single + * trailing NULL (00H) character when necessary to achieve even + * length." + **/ + + if (!value.empty() && + value[value.size() - 1] == '\0') + { + value.resize(value.size() - 1); + } + + break; + } + + /** + * TODO implement other VR + **/ + + default: + // No padding is applicable to this VR + break; + } + } + + + static bool ReadNextTag(DicomTag& tag, + ValueRepresentation& vr, + std::string& value, + const char* dicom, + size_t size, + size_t& position) + { + /** + * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2 + * This function reads a data element with Explicit VR encoded using Little-Endian. + **/ + + if (position + 6 > size) + { + return false; + } + + tag = DicomTag(ReadUnsignedInteger16(dicom + position), + ReadUnsignedInteger16(dicom + position + 2)); + + vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true); + if (vr == ValueRepresentation_NotSupported) + { + return false; + } + + if (vr == ValueRepresentation_OtherByte || + vr == ValueRepresentation_OtherDouble || + vr == ValueRepresentation_OtherFloat || + vr == ValueRepresentation_OtherLong || + vr == ValueRepresentation_OtherWord || + vr == ValueRepresentation_Sequence || + vr == ValueRepresentation_UnlimitedCharacters || + vr == ValueRepresentation_UniversalResource || + vr == ValueRepresentation_UnlimitedText || + vr == ValueRepresentation_Unknown) // Note that "UN" should never appear in the Meta Information + { + if (position + 12 > size) + { + return false; + } + + uint32_t length = ReadUnsignedInteger32(dicom + position + 8); + + if (position + 12 + length > size) + { + return false; + } + + value.assign(dicom + position + 12, length); + position += (12 + length); + } + else + { + if (position + 8 > size) + { + return false; + } + + uint16_t length = ReadUnsignedInteger16(dicom + position + 6); + + if (position + 8 + length > size) + { + return false; + } + + value.assign(dicom + position + 8, length); + position += (8 + length); + } + + if (!ValidateTag(vr, value)) + { + return false; + } + + RemoveTagPadding(value, vr); + + return true; + } + + + bool DicomMap::ParseDicomMetaInformation(DicomMap& result, + const char* dicom, + size_t size) + { + /** + * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html + * According to Table 7.1-1, besides the "DICM" DICOM prefix, the + * file preamble (i.e. dicom[0..127]) should not be taken into + * 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 false; + } + + + /** + * The DICOM File Meta Information must be encoded using the + * Explicit VR Little Endian Transfer Syntax + * (UID=1.2.840.10008.1.2.1). + **/ + + result.Clear(); + + // First, we read the "File Meta Information Group Length" tag + // (0002,0000) to know where to stop reading the meta header + size_t position = 132; + + DicomTag tag(0x0000, 0x0000); // Dummy initialization + ValueRepresentation vr; + std::string value; + if (!ReadNextTag(tag, vr, value, dicom, size, position) || + tag.GetGroup() != 0x0002 || + tag.GetElement() != 0x0000 || + vr != ValueRepresentation_UnsignedLong || + value.size() != 4) + { + return false; + } + + size_t stopPosition = position + ReadUnsignedInteger32(value.c_str()); + if (stopPosition > size) + { + return false; + } + + while (position < stopPosition) + { + if (ReadNextTag(tag, vr, value, dicom, size, position)) + { + result.SetValue(tag, value, IsBinaryValueRepresentation(vr)); + } + else + { + return false; + } + } + + return true; + } + + + static std::string ValueAsString(const DicomMap& summary, + const DicomTag& tag) + { + const DicomValue& value = summary.GetValue(tag); + if (value.IsNull()) + { + return "(null)"; + } + else + { + return value.GetContent(); + } + } + + + void DicomMap::LogMissingTagsForStore() const + { + std::string s, t; + + if (HasTag(DICOM_TAG_PATIENT_ID)) + { + if (t.size() > 0) + t += ", "; + t += "PatientID=" + ValueAsString(*this, DICOM_TAG_PATIENT_ID); + } + else + { + if (s.size() > 0) + s += ", "; + s += "PatientID"; + } + + if (HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "StudyInstanceUID=" + ValueAsString(*this, DICOM_TAG_STUDY_INSTANCE_UID); + } + else + { + if (s.size() > 0) + s += ", "; + s += "StudyInstanceUID"; + } + + if (HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SeriesInstanceUID=" + ValueAsString(*this, DICOM_TAG_SERIES_INSTANCE_UID); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SeriesInstanceUID"; + } + + if (HasTag(DICOM_TAG_SOP_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SOPInstanceUID=" + ValueAsString(*this, DICOM_TAG_SOP_INSTANCE_UID); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SOPInstanceUID"; + } + + if (t.size() == 0) + { + LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; + } + else + { + LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; + } + } + + + bool DicomMap::CopyToString(std::string& result, + const DicomTag& tag, + bool allowBinary) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->CopyToString(result, allowBinary); + } + } + + bool DicomMap::ParseInteger32(int32_t& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseInteger32(result); + } + } + + bool DicomMap::ParseInteger64(int64_t& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseInteger64(result); + } + } + + bool DicomMap::ParseUnsignedInteger32(uint32_t& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseUnsignedInteger32(result); + } + } + + bool DicomMap::ParseUnsignedInteger64(uint64_t& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseUnsignedInteger64(result); + } + } + + bool DicomMap::ParseFloat(float& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseFloat(result); + } + } + + bool DicomMap::ParseDouble(double& result, + const DicomTag& tag) const + { + const DicomValue* value = TestAndGetValue(tag); + + if (value == NULL) + { + return false; + } + else + { + return value->ParseDouble(result); + } + } + + + void DicomMap::Serialize(Json::Value& target) const + { + target = Json::objectValue; + + for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it) + { + assert(it->second != NULL); + + std::string tag = it->first.Format(); + + Json::Value value; + it->second->Serialize(value); + + target[tag] = value; + } + } + + + void DicomMap::Unserialize(const Json::Value& source) + { + Clear(); + + if (source.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members tags = source.getMemberNames(); + + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag(0, 0); + + if (!DicomTag::ParseHexadecimal(tag, tags[i].c_str()) || + map_.find(tag) != map_.end()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::auto_ptr<DicomValue> value(new DicomValue); + value->Unserialize(source[tags[i]]); + + map_[tag] = value.release(); + } + } }
--- a/Core/DicomFormat/DicomMap.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomMap.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +48,7 @@ private: friend class DicomArray; friend class FromDcmtkBridge; - friend class ToDcmtkBridge; + friend class ParsedDicomFile; typedef std::map<DicomTag, DicomValue*> Map; @@ -88,6 +89,17 @@ void Clear(); + void SetNullValue(uint16_t group, + uint16_t element) + { + SetValue(group, element, new DicomValue); + } + + void SetNullValue(const DicomTag& tag) + { + SetValue(tag, new DicomValue); + } + void SetValue(uint16_t group, uint16_t element, const DicomValue& value) @@ -102,16 +114,18 @@ } void SetValue(const DicomTag& tag, - const std::string& str) + const std::string& str, + bool isBinary) { - SetValue(tag, new DicomValue(str, false)); + SetValue(tag, new DicomValue(str, isBinary)); } void SetValue(uint16_t group, uint16_t element, - const std::string& str) + const std::string& str, + bool isBinary) { - SetValue(group, element, new DicomValue(str, false)); + SetValue(group, element, new DicomValue(str, isBinary)); } bool HasTag(uint16_t group, uint16_t element) const @@ -169,12 +183,42 @@ static void GetMainDicomTags(std::set<DicomTag>& result); - void Print(FILE* fp) const; - void GetTags(std::set<DicomTag>& tags) const; static void LoadMainDicomTags(const DicomTag*& tags, size_t& size, ResourceType level); + + static bool ParseDicomMetaInformation(DicomMap& result, + const char* dicom, + size_t size); + + void LogMissingTagsForStore() const; + + bool CopyToString(std::string& result, + const DicomTag& tag, + bool allowBinary) const; + + bool ParseInteger32(int32_t& result, + const DicomTag& tag) const; + + bool ParseInteger64(int64_t& result, + const DicomTag& tag) const; + + bool ParseUnsignedInteger32(uint32_t& result, + const DicomTag& tag) const; + + bool ParseUnsignedInteger64(uint64_t& result, + const DicomTag& tag) const; + + bool ParseFloat(float& result, + const DicomTag& tag) const; + + bool ParseDouble(double& result, + const DicomTag& tag) const; + + void Serialize(Json::Value& target) const; + + void Unserialize(const Json::Value& source); }; }
--- a/Core/DicomFormat/DicomTag.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomTag.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +39,32 @@ #include <iostream> #include <iomanip> #include <stdio.h> +#include <string.h> namespace Orthanc -{ +{ + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + bool DicomTag::operator< (const DicomTag& other) const { if (group_ < other.group_) @@ -73,6 +97,49 @@ } + bool DicomTag::ParseHexadecimal(DicomTag& tag, + const char* value) + { + size_t length = strlen(value); + + if (length == 9 && + isxdigit(value[0]) && + isxdigit(value[1]) && + isxdigit(value[2]) && + isxdigit(value[3]) && + (value[4] == '-' || value[4] == ',') && + isxdigit(value[5]) && + isxdigit(value[6]) && + isxdigit(value[7]) && + isxdigit(value[8])) + { + uint16_t group = GetTagValue(value); + uint16_t element = GetTagValue(value + 5); + tag = DicomTag(group, element); + return true; + } + else if (length == 8 && + isxdigit(value[0]) && + isxdigit(value[1]) && + isxdigit(value[2]) && + isxdigit(value[3]) && + isxdigit(value[4]) && + isxdigit(value[5]) && + isxdigit(value[6]) && + isxdigit(value[7])) + { + uint16_t group = GetTagValue(value); + uint16_t element = GetTagValue(value + 4); + tag = DicomTag(group, element); + return true; + } + else + { + return false; + } + } + + const char* DicomTag::GetMainTagsName() const { if (*this == DICOM_TAG_ACCESSION_NUMBER)
--- a/Core/DicomFormat/DicomTag.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomTag.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -66,6 +67,11 @@ return element_; } + bool IsPrivate() const + { + return group_ % 2 == 1; + } + const char* GetMainTagsName() const; bool operator< (const DicomTag& other) const; @@ -82,6 +88,9 @@ std::string Format() const; + static bool ParseHexadecimal(DicomTag& tag, + const char* value); + friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); static void AddTagsForModule(std::set<DicomTag>& target, @@ -95,6 +104,7 @@ static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e); static const DicomTag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d); static const DicomTag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010); + static const DicomTag DICOM_TAG_TRANSFER_SYNTAX_UID(0x0002, 0x0010); static const DicomTag DICOM_TAG_IMAGE_INDEX(0x0054, 0x1330); static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013); @@ -122,6 +132,7 @@ static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100); // Tags for C-FIND and C-MOVE + static const DicomTag DICOM_TAG_MESSAGE_ID(0x0000, 0x0110); static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005); static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052); static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061); @@ -152,4 +163,47 @@ static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031); static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020); static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030); + + // Various tags + static const DicomTag DICOM_TAG_SERIES_TYPE(0x0054, 0x1000); + static const DicomTag DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION(0x0032, 0x1060); + static const DicomTag DICOM_TAG_INSTITUTION_NAME(0x0008, 0x0080); + static const DicomTag DICOM_TAG_REQUESTING_PHYSICIAN(0x0032, 0x1032); + static const DicomTag DICOM_TAG_REFERRING_PHYSICIAN_NAME(0x0008, 0x0090); + static const DicomTag DICOM_TAG_OPERATOR_NAME(0x0008, 0x1070); + static const DicomTag DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION(0x0040, 0x0254); + static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000); + static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400); + static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010); + + // Tags used within the Stone of Orthanc + static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009); + static const DicomTag DICOM_TAG_GRID_FRAME_OFFSET_VECTOR(0x3004, 0x000c); + static const DicomTag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030); + static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052); + static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053); + static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050); + static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050); + static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051); + static const DicomTag DICOM_TAG_DOSE_GRID_SCALING(0x3004, 0x000e); + + // Counting patients, studies and series + // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200); + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202); + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204); + static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206); + static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208); + static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209); + static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062); + + // Tags to preserve relationships during anonymization + static const DicomTag DICOM_TAG_REFERENCED_IMAGE_SEQUENCE(0x0008, 0x1140); + static const DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); + static const DicomTag DICOM_TAG_SOURCE_IMAGE_SEQUENCE(0x0008, 0x2112); + static const DicomTag DICOM_TAG_FRAME_OF_REFERENCE_UID(0x0020, 0x0052); + static const DicomTag DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID(0x3006, 0x0024); + static const DicomTag DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID(0x3006, 0x00c2); + static const DicomTag DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE(0x0040, 0xa375); + static const DicomTag DICOM_TAG_REFERENCED_SERIES_SEQUENCE(0x0008, 0x1115); }
--- a/Core/DicomFormat/DicomValue.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomValue.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,11 @@ #include "DicomValue.h" #include "../OrthancException.h" +#include "../SerializationToolbox.h" #include "../Toolbox.h" +#include <boost/lexical_cast.hpp> + namespace Orthanc { DicomValue::DicomValue(const DicomValue& other) : @@ -81,7 +85,7 @@ } -#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 +#if ORTHANC_ENABLE_BASE64 == 1 void DicomValue::FormatDataUriScheme(std::string& target, const std::string& mime) const { @@ -90,4 +94,165 @@ } #endif + + template <typename T, + bool allowSigned> + static bool ParseValue(T& result, + const DicomValue& source) + { + if (source.IsBinary() || + source.IsNull()) + { + return false; + } + + try + { + std::string value = Toolbox::StripSpaces(source.GetContent()); + if (value.empty()) + { + return false; + } + + if (!allowSigned && + value[0] == '-') + { + return false; + } + + result = boost::lexical_cast<T>(value); + return true; + } + catch (boost::bad_lexical_cast&) + { + return false; + } + } + + bool DicomValue::ParseInteger32(int32_t& result) const + { + int64_t tmp; + if (ParseValue<int64_t, true>(tmp, *this)) + { + result = static_cast<int32_t>(tmp); + return (tmp == static_cast<int64_t>(result)); // Check no overflow occurs + } + else + { + return false; + } + } + + bool DicomValue::ParseInteger64(int64_t& result) const + { + return ParseValue<int64_t, true>(result, *this); + } + + bool DicomValue::ParseUnsignedInteger32(uint32_t& result) const + { + uint64_t tmp; + if (ParseValue<uint64_t, false>(tmp, *this)) + { + result = static_cast<uint32_t>(tmp); + return (tmp == static_cast<uint64_t>(result)); // Check no overflow occurs + } + else + { + return false; + } + } + + bool DicomValue::ParseUnsignedInteger64(uint64_t& result) const + { + return ParseValue<uint64_t, false>(result, *this); + } + + bool DicomValue::ParseFloat(float& result) const + { + return ParseValue<float, true>(result, *this); + } + + bool DicomValue::ParseDouble(double& result) const + { + return ParseValue<double, true>(result, *this); + } + + bool DicomValue::CopyToString(std::string& result, + bool allowBinary) const + { + if (IsNull()) + { + return false; + } + else if (IsBinary() && !allowBinary) + { + return false; + } + else + { + result.assign(content_); + return true; + } + } + + + static const char* KEY_TYPE = "Type"; + static const char* KEY_CONTENT = "Content"; + + void DicomValue::Serialize(Json::Value& target) const + { + target = Json::objectValue; + + switch (type_) + { + case Type_Null: + target[KEY_TYPE] = "Null"; + break; + + case Type_String: + target[KEY_TYPE] = "String"; + target[KEY_CONTENT] = content_; + break; + + case Type_Binary: + { + target[KEY_TYPE] = "Binary"; + + std::string base64; + Toolbox::EncodeBase64(base64, content_); + target[KEY_CONTENT] = base64; + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + void DicomValue::Unserialize(const Json::Value& source) + { + std::string type = SerializationToolbox::ReadString(source, KEY_TYPE); + + if (type == "Null") + { + type_ = Type_Null; + content_.clear(); + } + else if (type == "String") + { + type_ = Type_String; + content_ = SerializationToolbox::ReadString(source, KEY_CONTENT); + } + else if (type == "Binary") + { + type_ = Type_Binary; + + const std::string base64 =SerializationToolbox::ReadString(source, KEY_CONTENT); + Toolbox::DecodeBase64(content_, base64); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } }
--- a/Core/DicomFormat/DicomValue.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/DicomFormat/DicomValue.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,8 +33,15 @@ #pragma once +#include <stdint.h> #include <string> #include <boost/noncopyable.hpp> +#include <json/value.h> + +#if !defined(ORTHANC_ENABLE_BASE64) +# error The macro ORTHANC_ENABLE_BASE64 must be defined +#endif + namespace Orthanc { @@ -78,7 +86,7 @@ DicomValue* Clone() const; -#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 +#if ORTHANC_ENABLE_BASE64 == 1 void FormatDataUriScheme(std::string& target, const std::string& mime) const; @@ -87,5 +95,24 @@ FormatDataUriScheme(target, "application/octet-stream"); } #endif + + bool CopyToString(std::string& result, + bool allowBinary) const; + + bool ParseInteger32(int32_t& result) const; + + bool ParseInteger64(int64_t& result) const; + + bool ParseUnsignedInteger32(uint32_t& result) const; + + bool ParseUnsignedInteger64(uint64_t& result) const; + + bool ParseFloat(float& result) const; + + bool ParseDouble(double& result) const; + + void Serialize(Json::Value& target) const; + + void Unserialize(const Json::Value& source); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomFindAnswers.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,178 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomFindAnswers.h" + +#include "../DicomParsing/FromDcmtkBridge.h" +#include "../OrthancException.h" + +#include <memory> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <boost/noncopyable.hpp> + + +namespace Orthanc +{ + void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer) + { + std::auto_ptr<ParsedDicomFile> protection(answer); + + if (isWorklist_) + { + // These lines are necessary when serving worklists, otherwise + // Orthanc does not behave as "wlmscpfs" + protection->Remove(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID); + protection->Remove(DICOM_TAG_SOP_INSTANCE_UID); + } + + protection->ChangeEncoding(encoding_); + + answers_.push_back(protection.release()); + } + + + DicomFindAnswers::DicomFindAnswers(bool isWorklist) : + encoding_(GetDefaultDicomEncoding()), + isWorklist_(isWorklist), + complete_(true) + { + } + + + void DicomFindAnswers::SetEncoding(Encoding encoding) + { + for (size_t i = 0; i < answers_.size(); i++) + { + assert(answers_[i] != NULL); + answers_[i]->ChangeEncoding(encoding); + } + + encoding_ = encoding; + } + + + void DicomFindAnswers::SetWorklist(bool isWorklist) + { + if (answers_.empty()) + { + isWorklist_ = isWorklist; + } + else + { + // This set of answers is not empty anymore, cannot change its type + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomFindAnswers::Clear() + { + for (size_t i = 0; i < answers_.size(); i++) + { + assert(answers_[i] != NULL); + delete answers_[i]; + } + + answers_.clear(); + } + + + void DicomFindAnswers::Reserve(size_t size) + { + if (size > answers_.size()) + { + answers_.reserve(size); + } + } + + + void DicomFindAnswers::Add(const DicomMap& map) + { + AddAnswerInternal(new ParsedDicomFile(map, encoding_)); + } + + + void DicomFindAnswers::Add(ParsedDicomFile& dicom) + { + AddAnswerInternal(dicom.Clone(true)); + } + + void DicomFindAnswers::Add(const void* dicom, + size_t size) + { + AddAnswerInternal(new ParsedDicomFile(dicom, size)); + } + + + ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const + { + if (index < answers_.size()) + { + return *answers_[index]; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const + { + return new DcmDataset(*GetAnswer(index).GetDcmtkObject().getDataset()); + } + + + void DicomFindAnswers::ToJson(Json::Value& target, + size_t index, + bool simplify) const + { + DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full); + GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0); + } + + + void DicomFindAnswers::ToJson(Json::Value& target, + bool simplify) const + { + target = Json::arrayValue; + + for (size_t i = 0; i < GetSize(); i++) + { + Json::Value answer; + ToJson(answer, i, simplify); + target.append(answer); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomFindAnswers.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,109 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../DicomParsing/ParsedDicomFile.h" + +namespace Orthanc +{ + class DicomFindAnswers : public boost::noncopyable + { + private: + Encoding encoding_; + bool isWorklist_; + std::vector<ParsedDicomFile*> answers_; + bool complete_; + + void AddAnswerInternal(ParsedDicomFile* answer); + + public: + DicomFindAnswers(bool isWorklist); + + ~DicomFindAnswers() + { + Clear(); + } + + Encoding GetEncoding() const + { + return encoding_; + } + + void SetEncoding(Encoding encoding); + + void SetWorklist(bool isWorklist); + + bool IsWorklist() const + { + return isWorklist_; + } + + void Clear(); + + void Reserve(size_t index); + + void Add(const DicomMap& map); + + void Add(ParsedDicomFile& dicom); + + void Add(const void* dicom, + size_t size); + + size_t GetSize() const + { + return answers_.size(); + } + + ParsedDicomFile& GetAnswer(size_t index) const; + + DcmDataset* ExtractDcmDataset(size_t index) const; + + void ToJson(Json::Value& target, + bool simplify) const; + + void ToJson(Json::Value& target, + size_t index, + bool simplify) const; + + bool IsComplete() const + { + return complete_; + } + + void SetComplete(bool isComplete) + { + complete_ = isComplete; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomServer.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,382 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomServer.h" + +#include "../../Core/Logging.h" +#include "../../Core/MultiThreading/RunnableWorkersPool.h" +#include "../../Core/OrthancException.h" +#include "../../Core/Toolbox.h" +#include "Internals/CommandDispatcher.h" + +#include <boost/thread.hpp> + +#if defined(__linux__) +#include <cstdlib> +#endif + + +namespace Orthanc +{ + struct DicomServer::PImpl + { + boost::thread thread_; + T_ASC_Network *network_; + std::auto_ptr<RunnableWorkersPool> workers_; + }; + + + void DicomServer::ServerThread(DicomServer* server) + { + LOG(INFO) << "DICOM server started"; + + while (server->continue_) + { + /* 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_)); + + try + { + if (dispatcher.get() != NULL) + { + server->pimpl_->workers_->Add(dispatcher.release()); + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Exception in the DICOM server thread: " << e.What(); + } + } + + LOG(INFO) << "DICOM server stopping"; + } + + + DicomServer::DicomServer() : + pimpl_(new PImpl), + aet_("ANY-SCP") + { + port_ = 104; + modalities_ = NULL; + findRequestHandlerFactory_ = NULL; + moveRequestHandlerFactory_ = NULL; + storeRequestHandlerFactory_ = NULL; + worklistRequestHandlerFactory_ = NULL; + applicationEntityFilter_ = NULL; + checkCalledAet_ = true; + associationTimeout_ = 30; + continue_ = false; + } + + DicomServer::~DicomServer() + { + if (continue_) + { + LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + void DicomServer::SetPortNumber(uint16_t port) + { + Stop(); + port_ = port; + } + + uint16_t DicomServer::GetPortNumber() const + { + return port_; + } + + void DicomServer::SetAssociationTimeout(uint32_t seconds) + { + LOG(INFO) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): " + << seconds << " seconds (0 = no timeout)"; + + Stop(); + associationTimeout_ = seconds; + } + + uint32_t DicomServer::GetAssociationTimeout() const + { + return associationTimeout_; + } + + + void DicomServer::SetCalledApplicationEntityTitleCheck(bool check) + { + Stop(); + checkCalledAet_ = check; + } + + bool DicomServer::HasCalledApplicationEntityTitleCheck() const + { + return checkCalledAet_; + } + + void DicomServer::SetApplicationEntityTitle(const std::string& aet) + { + if (aet.size() == 0) + { + throw OrthancException(ErrorCode_BadApplicationEntityTitle); + } + + if (aet.size() > 16) + { + throw OrthancException(ErrorCode_BadApplicationEntityTitle); + } + + for (size_t i = 0; i < aet.size(); i++) + { + if (!(aet[i] == '-' || + aet[i] == '_' || + isdigit(aet[i]) || + (aet[i] >= 'A' && aet[i] <= 'Z'))) + { + LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\""; + break; + } + } + + Stop(); + aet_ = aet; + } + + const std::string& DicomServer::GetApplicationEntityTitle() const + { + return aet_; + } + + void DicomServer::SetRemoteModalities(IRemoteModalities& modalities) + { + Stop(); + modalities_ = &modalities; + } + + DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const + { + if (modalities_ == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *modalities_; + } + } + + void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory) + { + Stop(); + findRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasFindRequestHandlerFactory() const + { + return (findRequestHandlerFactory_ != NULL); + } + + IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const + { + if (HasFindRequestHandlerFactory()) + { + return *findRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoCFindHandler); + } + } + + void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory) + { + Stop(); + moveRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasMoveRequestHandlerFactory() const + { + return (moveRequestHandlerFactory_ != NULL); + } + + IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const + { + if (HasMoveRequestHandlerFactory()) + { + return *moveRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoCMoveHandler); + } + } + + void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory) + { + Stop(); + storeRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasStoreRequestHandlerFactory() const + { + return (storeRequestHandlerFactory_ != NULL); + } + + IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const + { + if (HasStoreRequestHandlerFactory()) + { + return *storeRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoCStoreHandler); + } + } + + void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory) + { + Stop(); + worklistRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasWorklistRequestHandlerFactory() const + { + return (worklistRequestHandlerFactory_ != NULL); + } + + IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const + { + if (HasWorklistRequestHandlerFactory()) + { + return *worklistRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoWorklistHandler); + } + } + + void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory) + { + Stop(); + applicationEntityFilter_ = &factory; + } + + bool DicomServer::HasApplicationEntityFilter() const + { + return (applicationEntityFilter_ != NULL); + } + + IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const + { + if (HasApplicationEntityFilter()) + { + return *applicationEntityFilter_; + } + else + { + throw OrthancException(ErrorCode_NoApplicationEntityFilter); + } + } + + void DicomServer::Start() + { + if (modalities_ == NULL) + { + LOG(ERROR) << "No list of modalities was provided to the DICOM server"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + Stop(); + + /* initialize network, i.e. create an instance of T_ASC_Network*. */ + OFCondition cond = ASC_initializeNetwork + (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_); + if (cond.bad()) + { + LOG(ERROR) << "cannot create network: " << cond.text(); + throw OrthancException(ErrorCode_DicomPortInUse); + } + + continue_ = true; + pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? + pimpl_->thread_ = boost::thread(ServerThread, this); + } + + + void DicomServer::Stop() + { + if (continue_) + { + continue_ = false; + + if (pimpl_->thread_.joinable()) + { + pimpl_->thread_.join(); + } + + pimpl_->workers_.reset(NULL); + + /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ + /* is the counterpart of ASC_initializeNetwork(...) which was called above. */ + OFCondition cond = ASC_dropNetwork(&pimpl_->network_); + if (cond.bad()) + { + LOG(ERROR) << "Error while dropping the network: " << cond.text(); + } + } + } + + + bool DicomServer::IsMyAETitle(const std::string& aet) const + { + if (modalities_ == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (!HasCalledApplicationEntityTitleCheck()) + { + // OK, no check on the AET. + return true; + } + else + { + return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle()); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomServer.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,136 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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_NETWORKING != 1 +# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 +#endif + +#include "IFindRequestHandlerFactory.h" +#include "IMoveRequestHandlerFactory.h" +#include "IStoreRequestHandlerFactory.h" +#include "IWorklistRequestHandlerFactory.h" +#include "IApplicationEntityFilter.h" +#include "RemoteModalityParameters.h" + +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + + +namespace Orthanc +{ + class DicomServer : public boost::noncopyable + { + public: + // WARNING: The methods of this class must be thread-safe + class IRemoteModalities : public boost::noncopyable + { + public: + virtual ~IRemoteModalities() + { + } + + virtual bool IsSameAETitle(const std::string& aet1, + const std::string& aet2) = 0; + + virtual bool LookupAETitle(RemoteModalityParameters& modality, + const std::string& aet) = 0; + }; + + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + bool checkCalledAet_; + std::string aet_; + uint16_t port_; + bool continue_; + uint32_t associationTimeout_; + IRemoteModalities* modalities_; + IFindRequestHandlerFactory* findRequestHandlerFactory_; + IMoveRequestHandlerFactory* moveRequestHandlerFactory_; + IStoreRequestHandlerFactory* storeRequestHandlerFactory_; + IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_; + IApplicationEntityFilter* applicationEntityFilter_; + + static void ServerThread(DicomServer* server); + + public: + DicomServer(); + + ~DicomServer(); + + void SetPortNumber(uint16_t port); + uint16_t GetPortNumber() const; + + void SetAssociationTimeout(uint32_t seconds); + uint32_t GetAssociationTimeout() const; + + void SetCalledApplicationEntityTitleCheck(bool check); + bool HasCalledApplicationEntityTitleCheck() const; + + void SetApplicationEntityTitle(const std::string& aet); + const std::string& GetApplicationEntityTitle() const; + + void SetRemoteModalities(IRemoteModalities& modalities); + IRemoteModalities& GetRemoteModalities() const; + + void SetFindRequestHandlerFactory(IFindRequestHandlerFactory& handler); + bool HasFindRequestHandlerFactory() const; + IFindRequestHandlerFactory& GetFindRequestHandlerFactory() const; + + void SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& handler); + bool HasMoveRequestHandlerFactory() const; + IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const; + + void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler); + bool HasStoreRequestHandlerFactory() const; + IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const; + + void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler); + bool HasWorklistRequestHandlerFactory() const; + IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const; + + void SetApplicationEntityFilter(IApplicationEntityFilter& handler); + bool HasApplicationEntityFilter() const; + IApplicationEntityFilter& GetApplicationEntityFilter() const; + + void Start(); + + void Stop(); + + bool IsMyAETitle(const std::string& aet) const; + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1258 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../PrecompiledHeaders.h" +#include "DicomUserConnection.h" + +#include "../DicomFormat/DicomArray.h" +#include "../Logging.h" +#include "../OrthancException.h" +#include "../DicomParsing/FromDcmtkBridge.h" +#include "../DicomParsing/ToDcmtkBridge.h" + +#include <dcmtk/dcmdata/dcistrmb.h> +#include <dcmtk/dcmdata/dcistrmf.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmnet/diutil.h> + +#include <set> + + +#ifdef _WIN32 +/** + * "The maximum length, in bytes, of the string returned in the buffer + * pointed to by the name parameter is dependent on the namespace provider, + * but this string must be 256 bytes or less. + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738527(v=vs.85).aspx + **/ +# define HOST_NAME_MAX 256 +# include <winsock.h> +#endif + + +#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) +/** + * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that + * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an + * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect + * that the result will fit." + * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html + **/ +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif + + +static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; + +/** + * "If we have more than 64 storage SOP classes, tools such as + * storescu will fail because they attempt to negotiate two + * presentation contexts for each SOP class, and there is a total + * limit of 128 contexts for one association." + **/ +static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64; + + +namespace Orthanc +{ + // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds + static uint32_t defaultTimeout_ = 10; + + struct DicomUserConnection::PImpl + { + // Connection state + uint32_t dimseTimeout_; + uint32_t acseTimeout_; + T_ASC_Network* net_; + T_ASC_Parameters* params_; + T_ASC_Association* assoc_; + + bool IsOpen() const + { + return assoc_ != NULL; + } + + void CheckIsOpen() const; + + void Store(DcmInputStream& is, + DicomUserConnection& connection, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + }; + + + static void Check(const OFCondition& cond) + { + if (cond.bad()) + { + LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text()); + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + void DicomUserConnection::PImpl::CheckIsOpen() const + { + if (!IsOpen()) + { + LOG(ERROR) << "DicomUserConnection: First open the connection"; + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + + void DicomUserConnection::CheckIsOpen() const + { + pimpl_->CheckIsOpen(); + } + + + static void RegisterStorageSOPClass(T_ASC_Parameters* params, + unsigned int& presentationContextId, + const std::string& sopClass, + const char* asPreferred[], + std::vector<const char*>& asFallback) + { + Check(ASC_addPresentationContext(params, presentationContextId, + sopClass.c_str(), asPreferred, 1)); + presentationContextId += 2; + + if (asFallback.size() > 0) + { + Check(ASC_addPresentationContext(params, presentationContextId, + sopClass.c_str(), &asFallback[0], asFallback.size())); + presentationContextId += 2; + } + } + + + void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) + { + // Flatten an array with the preferred transfer syntax + const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; + + // Setup the fallback transfer syntaxes + std::set<std::string> fallbackSyntaxes; + fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); + fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); + fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); + fallbackSyntaxes.erase(preferredTransferSyntax); + + // Flatten an array with the fallback transfer syntaxes + std::vector<const char*> asFallback; + asFallback.reserve(fallbackSyntaxes.size()); + for (std::set<std::string>::const_iterator + it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) + { + asFallback.push_back(it->c_str()); + } + + CheckStorageSOPClassesInvariant(); + 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); + } + + for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); + it != storageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } + + for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); + it != defaultStorageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } + } + + + static bool IsGenericTransferSyntax(const std::string& syntax) + { + return (syntax == UID_LittleEndianExplicitTransferSyntax || + syntax == UID_BigEndianExplicitTransferSyntax || + syntax == UID_LittleEndianImplicitTransferSyntax); + } + + + void DicomUserConnection::PImpl::Store(DcmInputStream& is, + DicomUserConnection& connection, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + DcmFileFormat dcmff; + Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); + + // Determine the storage SOP class UID for this instance + static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016); + OFString sopClassUid; + if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good()) + { + connection.AddStorageSOPClass(sopClassUid.c_str()); + } + + // Determine whether a new presentation context must be + // negotiated, depending on the transfer syntax of this instance + DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); + const std::string syntax(xfer.getXferID()); + bool isGeneric = IsGenericTransferSyntax(syntax); + + bool renegotiate; + + if (!IsOpen()) + { + renegotiate = true; + } + else if (isGeneric) + { + // Are we making a generic-to-specific or specific-to-generic change of + // the transfer syntax? If this is the case, renegotiate the connection. + renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); + + if (renegotiate) + { + LOG(INFO) << "Use of non-generic transfer syntax: the C-Store associated must be renegotiated"; + } + } + else + { + // We are using a specific transfer syntax. Renegotiate if the + // current connection does not match this transfer syntax. + renegotiate = (syntax != connection.GetPreferredTransferSyntax()); + + if (renegotiate) + { + LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; + } + } + + if (renegotiate) + { + if (isGeneric) + { + connection.ResetPreferredTransferSyntax(); + } + else + { + connection.SetPreferredTransferSyntax(syntax); + } + } + + if (!connection.IsOpen()) + { + connection.Open(); + } + + // Figure out which SOP class and SOP instance is encapsulated in the file + DIC_UI sopClass; + DIC_UI sopInstance; + if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance)) + { + throw OrthancException(ErrorCode_NoSopClassOrInstance); + } + + // Figure out which of the accepted presentation contexts should be used + int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); + if (presID == 0) + { + const char *modalityName = dcmSOPClassUIDToModality(sopClass); + if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); + if (!modalityName) modalityName = "unknown SOP class"; + throw OrthancException(ErrorCode_NoPresentationContext); + } + + // Prepare the transmission of data + T_DIMSE_C_StoreRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = assoc_->nextMsgID++; + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; + request.DataSetType = DIMSE_DATASET_PRESENT; + strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN); + + if (!moveOriginatorAET.empty()) + { + strncpy(request.MoveOriginatorApplicationEntityTitle, + moveOriginatorAET.c_str(), DIC_AE_LEN); + request.opts = O_STORE_MOVEORIGINATORAETITLE; + + request.MoveOriginatorID = moveOriginatorID; // The type DIC_US is an alias for uint16_t + request.opts |= O_STORE_MOVEORIGINATORID; + } + + // Finally conduct transmission of data + T_DIMSE_C_StoreRSP rsp; + 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)); + + if (statusDetail != NULL) + { + delete statusDetail; + } + } + + + namespace + { + struct FindPayload + { + DicomFindAnswers* answers; + const char* level; + bool isWorklist; + }; + } + + + static void FindCallback( + /* in */ + void *callbackData, + T_DIMSE_C_FindRQ *request, /* original find request */ + int responseCount, + T_DIMSE_C_FindRSP *response, /* pending response received */ + DcmDataset *responseIdentifiers /* pending response identifiers */ + ) + { + FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData); + + if (responseIdentifiers != NULL) + { + if (payload.isWorklist) + { + ParsedDicomFile answer(*responseIdentifiers); + payload.answers->Add(answer); + } + else + { + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers); + + if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + { + m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false); + } + + payload.answers->Add(m); + } + } + } + + + static void FixFindQuery(DicomMap& fixedQuery, + ResourceType level, + const DicomMap& fields) + { + std::set<DicomTag> allowedTags; + + // WARNING: Do not add "break" or reorder items in this switch-case! + switch (level) + { + case ResourceType_Instance: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); + + case ResourceType_Series: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); + + case ResourceType_Study: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); + + case ResourceType_Patient: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + switch (level) + { + case ResourceType_Patient: + allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); + allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); + allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); + break; + + case ResourceType_Study: + allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); + allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); + allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); + allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY); + break; + + case ResourceType_Series: + allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); + break; + + default: + break; + } + + allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); + + DicomArray query(fields); + for (size_t i = 0; i < query.GetSize(); i++) + { + const DicomTag& tag = query.GetElement(i).GetTag(); + if (allowedTags.find(tag) == allowedTags.end()) + { + LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; + } + else + { + fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); + } + } + } + + + static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields, + ModalityManufacturer manufacturer) + { + // Fix outgoing C-Find requests issue for Syngo.Via and its + // solution was reported by Emsy Chan by private mail on + // 2015-06-17. According to Robert van Ommen (2015-11-30), the + // same fix is required for Agfa Impax. This was generalized for + // generic manufacturer since it seems to affect PhilipsADW, + // GEWAServer as well: + // https://bitbucket.org/sjodogne/orthanc/issues/31/ + + switch (manufacturer) + { + case ModalityManufacturer_GenericNoWildcardInDates: + case ModalityManufacturer_GenericNoUniversalWildcard: + { + std::auto_ptr<DicomMap> fix(fields.Clone()); + + std::set<DicomTag> tags; + fix->GetTags(tags); + + for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + // Replace a "*" wildcard query by an empty query ("") for + // "date" or "all" value representations depending on the + // type of manufacturer. + if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard || + (manufacturer == ModalityManufacturer_GenericNoWildcardInDates && + FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date)) + { + const DicomValue* value = fix->TestAndGetValue(*it); + + if (value != NULL && + !value->IsNull() && + value->GetContent() == "*") + { + fix->SetValue(*it, "", false); + } + } + } + + return new ParsedDicomFile(*fix); + } + + default: + return new ParsedDicomFile(fields); + } + } + + + static void ExecuteFind(DicomFindAnswers& answers, + T_ASC_Association* association, + DcmDataset* dataset, + const char* sopClass, + bool isWorklist, + const char* level, + uint32_t dimseTimeout) + { + assert(isWorklist ^ (level != NULL)); + + FindPayload payload; + payload.answers = &answers; + payload.level = level; + payload.isWorklist = isWorklist; + + // Figure out which of the accepted presentation contexts should be used + int presID = ASC_findAcceptedPresentationContextID(association, sopClass); + if (presID == 0) + { + throw OrthancException(ErrorCode_DicomFindUnavailable); + } + + T_DIMSE_C_FindRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = association->nextMsgID++; + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; + request.DataSetType = DIMSE_DATASET_PRESENT; + + T_DIMSE_C_FindRSP response; + DcmDataset* statusDetail = NULL; + OFCondition cond = DIMSE_findUser(association, presID, &request, dataset, + FindCallback, &payload, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ dimseTimeout, + &response, &statusDetail); + + if (statusDetail) + { + delete statusDetail; + } + + Check(cond); + } + + + void DicomUserConnection::Find(DicomFindAnswers& result, + ResourceType level, + const DicomMap& originalFields) + { + DicomMap fields; + FixFindQuery(fields, level, originalFields); + + CheckIsOpen(); + + std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); + DcmDataset* dataset = query->GetDcmtkObject().getDataset(); + + const char* clevel = NULL; + const char* sopClass = NULL; + + switch (level) + { + case ResourceType_Patient: + clevel = "PATIENT"; + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT"); + sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; + break; + + case ResourceType_Study: + clevel = "STUDY"; + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + break; + + case ResourceType_Series: + clevel = "SERIES"; + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + break; + + case ResourceType_Instance: + clevel = "INSTANCE"; + if (manufacturer_ == ModalityManufacturer_ClearCanvas || + manufacturer_ == ModalityManufacturer_Dcm4Chee) + { + // 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, DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } + + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + // Add the expected tags for this query level. + // WARNING: Do not reorder or add "break" in this switch-case! + switch (level) + { + case ResourceType_Instance: + // SOP Instance UID + if (!fields.HasTag(0x0008, 0x0018)) + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), ""); + + case ResourceType_Series: + // Series instance UID + if (!fields.HasTag(0x0020, 0x000e)) + DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), ""); + + case ResourceType_Study: + // Accession number + if (!fields.HasTag(0x0008, 0x0050)) + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), ""); + + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), ""); + + case ResourceType_Patient: + // Patient ID + if (!fields.HasTag(0x0010, 0x0020)) + DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), ""); + + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + assert(clevel != NULL && sopClass != NULL); + ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel, pimpl_->dimseTimeout_); + } + + + void DicomUserConnection::MoveInternal(const std::string& targetAet, + ResourceType level, + const DicomMap& fields) + { + CheckIsOpen(); + + std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); + DcmDataset* dataset = query->GetDcmtkObject().getDataset(); + + const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; + switch (level) + { + case ResourceType_Patient: + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT"); + break; + + case ResourceType_Study: + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY"); + break; + + case ResourceType_Series: + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES"); + break; + + case ResourceType_Instance: + if (manufacturer_ == ModalityManufacturer_ClearCanvas || + manufacturer_ == ModalityManufacturer_Dcm4Chee) + { + // 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, DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + // Figure out which of the accepted presentation contexts should be used + int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); + if (presID == 0) + { + throw OrthancException(ErrorCode_DicomMoveUnavailable); + } + + T_DIMSE_C_MoveRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = pimpl_->assoc_->nextMsgID++; + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; + request.DataSetType = DIMSE_DATASET_PRESENT; + strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); + + T_DIMSE_C_MoveRSP response; + DcmDataset* statusDetail = NULL; + DcmDataset* responseIdentifiers = NULL; + OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset, + NULL, NULL, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, + pimpl_->net_, NULL, NULL, + &response, &statusDetail, &responseIdentifiers); + + if (statusDetail) + { + delete statusDetail; + } + + if (responseIdentifiers) + { + delete responseIdentifiers; + } + + Check(cond); + } + + + void DicomUserConnection::ResetStorageSOPClasses() + { + CheckStorageSOPClassesInvariant(); + + storageSOPClasses_.clear(); + defaultStorageSOPClasses_.clear(); + + // Copy the short list of storage SOP classes from DCMTK, making + // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**). + + std::set<std::string> uncommon; + uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); + uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); + uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); + uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); + uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage); + + // Add the storage syntaxes for C-STORE + for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) + { + if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end()) + { + defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]); + } + } + + CheckStorageSOPClassesInvariant(); + } + + + void DicomUserConnection::DefaultSetup() + { + preferredTransferSyntax_ = DEFAULT_PREFERRED_TRANSFER_SYNTAX; + localAet_ = "STORESCU"; + remoteAet_ = "ANY-SCP"; + remoteHost_ = "127.0.0.1"; + remotePort_ = 104; + manufacturer_ = ModalityManufacturer_Generic; + + SetTimeout(defaultTimeout_); + pimpl_->net_ = NULL; + pimpl_->params_ = NULL; + pimpl_->assoc_ = NULL; + + // SOP classes for C-ECHO, C-FIND and C-MOVE (**) + reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); + reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); + reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); + reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); + reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel); + + ResetStorageSOPClasses(); + } + + + DicomUserConnection::DicomUserConnection() : + pimpl_(new PImpl) + { + DefaultSetup(); + } + + + DicomUserConnection::DicomUserConnection(const std::string& localAet, + const RemoteModalityParameters& remote) : + pimpl_(new PImpl) + { + DefaultSetup(); + SetLocalApplicationEntityTitle(localAet); + SetRemoteModality(remote); + } + + + DicomUserConnection::~DicomUserConnection() + { + Close(); + } + + + void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters) + { + SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); + SetRemoteHost(parameters.GetHost()); + SetRemotePort(parameters.GetPortNumber()); + SetRemoteManufacturer(parameters.GetManufacturer()); + } + + + void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) + { + if (localAet_ != aet) + { + Close(); + localAet_ = aet; + } + } + + void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet) + { + if (remoteAet_ != aet) + { + Close(); + remoteAet_ = aet; + } + } + + void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer) + { + if (manufacturer_ != manufacturer) + { + Close(); + manufacturer_ = manufacturer; + } + } + + void DicomUserConnection::ResetPreferredTransferSyntax() + { + SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX); + } + + void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax) + { + if (preferredTransferSyntax_ != preferredTransferSyntax) + { + Close(); + preferredTransferSyntax_ = preferredTransferSyntax; + } + } + + + void DicomUserConnection::SetRemoteHost(const std::string& host) + { + if (remoteHost_ != host) + { + if (host.size() > HOST_NAME_MAX - 10) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + Close(); + remoteHost_ = host; + } + } + + void DicomUserConnection::SetRemotePort(uint16_t port) + { + if (remotePort_ != port) + { + Close(); + remotePort_ = port; + } + } + + void DicomUserConnection::Open() + { + if (IsOpen()) + { + // Don't reopen the connection + return; + } + + LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() + << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host " + << GetRemoteHost() << ":" << GetRemotePort() + << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")"; + + Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_)); + Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); + + // Set this application's title and the called application's title in the params + Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL)); + + // Set the network addresses of the local and remote entities + char localHost[HOST_NAME_MAX]; + gethostname(localHost, HOST_NAME_MAX - 1); + + char remoteHostAndPort[HOST_NAME_MAX]; + +#ifdef _MSC_VER + _snprintf +#else + snprintf +#endif + (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_); + + Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort)); + + // Set various options + Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); + + SetupPresentationContexts(preferredTransferSyntax_); + + // Do the association + Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_)); + + if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0) + { + throw OrthancException(ErrorCode_NoPresentationContext); + } + } + + void DicomUserConnection::Close() + { + if (pimpl_->assoc_ != NULL) + { + ASC_releaseAssociation(pimpl_->assoc_); + ASC_destroyAssociation(&pimpl_->assoc_); + pimpl_->assoc_ = NULL; + pimpl_->params_ = NULL; + } + else + { + if (pimpl_->params_ != NULL) + { + ASC_destroyAssociationParameters(&pimpl_->params_); + pimpl_->params_ = NULL; + } + } + + if (pimpl_->net_ != NULL) + { + ASC_dropNetwork(&pimpl_->net_); + pimpl_->net_ = NULL; + } + } + + bool DicomUserConnection::IsOpen() const + { + return pimpl_->IsOpen(); + } + + void DicomUserConnection::Store(const char* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + // Prepare an input stream for the memory buffer + DcmInputBufferStream is; + if (size > 0) + is.setBuffer(buffer, size); + is.setEos(); + + pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); + } + + void DicomUserConnection::Store(const std::string& buffer, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + if (buffer.size() > 0) + Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size(), moveOriginatorAET, moveOriginatorID); + else + Store(NULL, 0, moveOriginatorAET, moveOriginatorID); + } + + void DicomUserConnection::StoreFile(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); + } + + bool DicomUserConnection::Echo() + { + CheckIsOpen(); + DIC_US status; + Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, + &status, NULL)); + return status == STATUS_Success; + } + + + static void TestAndCopyTag(DicomMap& result, + const DicomMap& source, + const DicomTag& tag) + { + if (!source.HasTag(tag)) + { + throw OrthancException(ErrorCode_BadRequest); + } + else + { + result.SetValue(tag, source.GetValue(tag)); + } + } + + + void DicomUserConnection::Move(const std::string& targetAet, + ResourceType level, + const DicomMap& findResult) + { + DicomMap move; + switch (level) + { + case ResourceType_Patient: + TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); + break; + + case ResourceType_Study: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + break; + + case ResourceType_Series: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); + break; + + case ResourceType_Instance: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + MoveInternal(targetAet, level, move); + } + + + void DicomUserConnection::Move(const std::string& targetAet, + const DicomMap& findResult) + { + if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + { + throw OrthancException(ErrorCode_InternalError); + } + + const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); + ResourceType level = StringToResourceType(tmp.c_str()); + + Move(targetAet, level, findResult); + } + + + void DicomUserConnection::MovePatient(const std::string& targetAet, + const std::string& patientId) + { + DicomMap query; + query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); + MoveInternal(targetAet, ResourceType_Patient, query); + } + + void DicomUserConnection::MoveStudy(const std::string& targetAet, + const std::string& studyUid) + { + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); + MoveInternal(targetAet, ResourceType_Study, query); + } + + void DicomUserConnection::MoveSeries(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid) + { + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); + query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); + MoveInternal(targetAet, ResourceType_Series, query); + } + + void DicomUserConnection::MoveInstance(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid, + const std::string& instanceUid) + { + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); + query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); + query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); + MoveInternal(targetAet, ResourceType_Instance, query); + } + + + void DicomUserConnection::SetTimeout(uint32_t seconds) + { + if (seconds == 0) + { + DisableTimeout(); + } + else + { + dcmConnectionTimeout.set(seconds); + pimpl_->dimseTimeout_ = seconds; + pimpl_->acseTimeout_ = 10; // Timeout used during association negociation + } + } + + + void DicomUserConnection::DisableTimeout() + { + /** + * Global timeout (seconds) for connecting to remote hosts. + * Default value is -1 which selects infinite timeout, i.e. blocking connect(). + */ + dcmConnectionTimeout.set(-1); + pimpl_->dimseTimeout_ = 0; + pimpl_->acseTimeout_ = 10; // Timeout used during association negociation + } + + + void DicomUserConnection::CheckStorageSOPClassesInvariant() const + { + assert(storageSOPClasses_.size() + + defaultStorageSOPClasses_.size() + + reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES); + } + + void DicomUserConnection::AddStorageSOPClass(const char* sop) + { + CheckStorageSOPClassesInvariant(); + + if (storageSOPClasses_.find(sop) != storageSOPClasses_.end()) + { + // This storage SOP class is already explicitly registered. Do + // nothing. + return; + } + + if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end()) + { + // This storage SOP class is not explicitly registered, but is + // used by default. Just register it explicitly. + defaultStorageSOPClasses_.erase(sop); + storageSOPClasses_.insert(sop); + + CheckStorageSOPClassesInvariant(); + return; + } + + // This storage SOP class is neither explicitly, nor implicitly + // registered. Close the connection and register it explicitly. + + Close(); + + if (reservedStorageSOPClasses_.size() + + storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) // (*) + { + // The maximum number of SOP classes is reached + ResetStorageSOPClasses(); + defaultStorageSOPClasses_.erase(sop); + } + else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + + defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) + { + // Make room in the default storage syntaxes + assert(!defaultStorageSOPClasses_.empty()); // Necessarily true because condition (*) is false + defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); + } + + // Explicitly register the new storage syntax + storageSOPClasses_.insert(sop); + + CheckStorageSOPClassesInvariant(); + } + + + void DicomUserConnection::FindWorklist(DicomFindAnswers& result, + ParsedDicomFile& query) + { + CheckIsOpen(); + + DcmDataset* dataset = query.GetDcmtkObject().getDataset(); + const char* sopClass = UID_FINDModalityWorklistInformationModel; + + ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_); + } + + + void DicomUserConnection::SetDefaultTimeout(uint32_t seconds) + { + LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " + << seconds << " seconds (0 = no timeout)"; + defaultTimeout_ = seconds; + } + + + bool DicomUserConnection::IsSameAssociation(const std::string& localAet, + const RemoteModalityParameters& remote) const + { + return (localAet_ == localAet && + remoteAet_ == remote.GetApplicationEntityTitle() && + remoteHost_ == remote.GetHost() && + remotePort_ == remote.GetPortNumber() && + manufacturer_ == remote.GetManufacturer()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomUserConnection.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,215 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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_NETWORKING != 1 +# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 +#endif + +#include "DicomFindAnswers.h" +#include "../Enumerations.h" +#include "RemoteModalityParameters.h" + +#include <stdint.h> +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <list> + +namespace Orthanc +{ + class DicomUserConnection : public boost::noncopyable + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + // Connection parameters + std::string preferredTransferSyntax_; + std::string localAet_; + std::string remoteAet_; + std::string remoteHost_; + uint16_t remotePort_; + ModalityManufacturer manufacturer_; + std::set<std::string> storageSOPClasses_; + std::list<std::string> reservedStorageSOPClasses_; + std::set<std::string> defaultStorageSOPClasses_; + + void CheckIsOpen() const; + + void SetupPresentationContexts(const std::string& preferredTransferSyntax); + + void MoveInternal(const std::string& targetAet, + ResourceType level, + const DicomMap& fields); + + void ResetStorageSOPClasses(); + + void CheckStorageSOPClassesInvariant() const; + + void DefaultSetup(); + + public: + DicomUserConnection(); + + ~DicomUserConnection(); + + // This constructor corresponds to behavior of the old class + // "ReusableDicomUserConnection", without the call to "Open()" + DicomUserConnection(const std::string& localAet, + const RemoteModalityParameters& remote); + + void SetRemoteModality(const RemoteModalityParameters& parameters); + + void SetLocalApplicationEntityTitle(const std::string& aet); + + const std::string& GetLocalApplicationEntityTitle() const + { + return localAet_; + } + + void SetRemoteApplicationEntityTitle(const std::string& aet); + + const std::string& GetRemoteApplicationEntityTitle() const + { + return remoteAet_; + } + + void SetRemoteHost(const std::string& host); + + const std::string& GetRemoteHost() const + { + return remoteHost_; + } + + void SetRemotePort(uint16_t port); + + uint16_t GetRemotePort() const + { + return remotePort_; + } + + void SetRemoteManufacturer(ModalityManufacturer manufacturer); + + ModalityManufacturer GetRemoteManufacturer() const + { + return manufacturer_; + } + + void ResetPreferredTransferSyntax(); + + void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax); + + const std::string& GetPreferredTransferSyntax() const + { + return preferredTransferSyntax_; + } + + void AddStorageSOPClass(const char* sop); + + void Open(); + + void Close(); + + bool IsOpen() const; + + bool Echo(); + + void Store(const char* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void Store(const char* buffer, + size_t size) + { + Store(buffer, size, "", 0); // Not a C-Move + } + + void Store(const std::string& buffer, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void Store(const std::string& buffer) + { + Store(buffer, "", 0); // Not a C-Move + } + + void StoreFile(const std::string& path, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void StoreFile(const std::string& path) + { + StoreFile(path, "", 0); // Not a C-Move + } + + void Find(DicomFindAnswers& result, + ResourceType level, + const DicomMap& fields); + + void Move(const std::string& targetAet, + ResourceType level, + const DicomMap& findResult); + + void Move(const std::string& targetAet, + const DicomMap& findResult); + + void MovePatient(const std::string& targetAet, + const std::string& patientId); + + void MoveStudy(const std::string& targetAet, + const std::string& studyUid); + + void MoveSeries(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid); + + void MoveInstance(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid, + const std::string& instanceUid); + + void SetTimeout(uint32_t seconds); + + void DisableTimeout(); + + void FindWorklist(DicomFindAnswers& result, + ParsedDicomFile& query); + + static void SetDefaultTimeout(uint32_t seconds); + + bool IsSameAssociation(const std::string& localAet, + const RemoteModalityParameters& remote) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IApplicationEntityFilter.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <string> + +namespace Orthanc +{ + class IApplicationEntityFilter : public boost::noncopyable + { + public: + virtual ~IApplicationEntityFilter() + { + } + + virtual bool IsAllowedConnection(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; + + virtual bool IsAllowedRequest(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + DicomRequestType type) = 0; + + virtual bool IsAllowedTransferSyntax(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + TransferSyntax syntax) = 0; + + virtual bool IsUnknownSopClassAccepted(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/IDicomConnectionManager.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,82 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_ENABLE_DCMTK_NETWORKING) +# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK_NETWORKING == 0 + +namespace Orthanc +{ + // DICOM networking is disabled, this is just a void class + class IDicomConnectionManager : public boost::noncopyable + { + public: + virtual ~IDicomConnectionManager() + { + } + }; +} + +#else + +#include "DicomUserConnection.h" + +namespace Orthanc +{ + class IDicomConnectionManager : public boost::noncopyable + { + public: + virtual ~IDicomConnectionManager() + { + } + + class IResource : public boost::noncopyable + { + public: + virtual ~IResource() + { + } + + virtual DicomUserConnection& GetConnection() = 0; + }; + + virtual IResource* AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote) = 0; + }; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IFindRequestHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomFindAnswers.h" + +#include <list> + +namespace Orthanc +{ + class IFindRequestHandler : public boost::noncopyable + { + public: + virtual ~IFindRequestHandler() + { + } + + virtual void Handle(DicomFindAnswers& answers, + const DicomMap& input, + const std::list<DicomTag>& sequencesToReturn, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IFindRequestHandlerFactory.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IFindRequestHandler.h" + +namespace Orthanc +{ + class IFindRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IFindRequestHandlerFactory() + { + } + + virtual IFindRequestHandler* ConstructFindRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IMoveRequestHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,79 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/DicomFormat/DicomMap.h" + +#include <vector> +#include <string> + + +namespace Orthanc +{ + class IMoveRequestIterator : public boost::noncopyable + { + public: + enum Status + { + Status_Success, + Status_Failure, + Status_Warning + }; + + virtual ~IMoveRequestIterator() + { + } + + virtual unsigned int GetSubOperationCount() const = 0; + + virtual Status DoNext() = 0; + }; + + + class IMoveRequestHandler + { + public: + virtual ~IMoveRequestHandler() + { + } + + virtual IMoveRequestIterator* Handle(const std::string& targetAet, + const DicomMap& input, + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet, + uint16_t originatorId) = 0; + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IMoveRequestHandlerFactory.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IMoveRequestHandler.h" + +namespace Orthanc +{ + class IMoveRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IMoveRequestHandlerFactory() + { + } + + virtual IMoveRequestHandler* ConstructMoveRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IStoreRequestHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/DicomFormat/DicomMap.h" + +#include <vector> +#include <string> +#include <json/json.h> + +namespace Orthanc +{ + class IStoreRequestHandler : public boost::noncopyable + { + public: + virtual ~IStoreRequestHandler() + { + } + + virtual void Handle(const std::string& dicomFile, + const DicomMap& dicomSummary, + const Json::Value& dicomJson, + 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/IStoreRequestHandlerFactory.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IStoreRequestHandler.h" + +namespace Orthanc +{ + class IStoreRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IStoreRequestHandlerFactory() + { + } + + virtual IStoreRequestHandler* ConstructStoreRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IWorklistRequestHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomFindAnswers.h" + +namespace Orthanc +{ + class IWorklistRequestHandler : public boost::noncopyable + { + public: + virtual ~IWorklistRequestHandler() + { + } + + virtual void Handle(DicomFindAnswers& answers, + ParsedDicomFile& query, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IWorklistRequestHandlerFactory.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IWorklistRequestHandler.h" + +namespace Orthanc +{ + class IWorklistRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IWorklistRequestHandlerFactory() + { + } + + virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,934 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../../PrecompiledHeaders.h" +#include "CommandDispatcher.h" + +#include "FindScp.h" +#include "StoreScp.h" +#include "MoveScp.h" +#include "../../Toolbox.h" +#include "../../Logging.h" + +#include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ +#include <boost/lexical_cast.hpp> + +static OFBool opt_rejectWithoutImplementationUID = OFFalse; + + + +static DUL_PRESENTATIONCONTEXT * +findPresentationContextID(LST_HEAD * head, + T_ASC_PresentationContextID presentationContextID) +{ + DUL_PRESENTATIONCONTEXT *pc; + LST_HEAD **l; + OFBool found = OFFalse; + + if (head == NULL) + return NULL; + + l = &head; + if (*l == NULL) + return NULL; + + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l)); + (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc)); + + while (pc && !found) { + if (pc->presentationContextID == presentationContextID) { + found = OFTrue; + } else { + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l)); + } + } + return pc; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithTransferSyntax( + T_ASC_Parameters * params, + const char* transferSyntax, + T_ASC_SC_ROLE acceptedRole) +{ + OFCondition cond = EC_Normal; + int n, i, k; + DUL_PRESENTATIONCONTEXT *dpc; + T_ASC_PresentationContext pc; + OFBool accepted = OFFalse; + OFBool abstractOK = OFFalse; + + n = ASC_countPresentationContexts(params); + for (i = 0; i < n; i++) + { + cond = ASC_getPresentationContext(params, i, &pc); + if (cond.bad()) return cond; + abstractOK = OFFalse; + accepted = OFFalse; + + if (dcmFindNameOfUID(pc.abstractSyntax) == NULL) + { + abstractOK = OFTrue; + + /* check the transfer syntax */ + for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++) + { + if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0) + { + accepted = OFTrue; + } + } + } + + if (accepted) + { + cond = ASC_acceptPresentationContext( + params, pc.presentationContextID, + transferSyntax, acceptedRole); + if (cond.bad()) return cond; + } else { + T_ASC_P_ResultReason reason; + + /* do not refuse if already accepted */ + dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext, + pc.presentationContextID); + if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))) + { + + if (abstractOK) { + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + } else { + reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED; + } + /* + * If previously this presentation context was refused + * because of bad transfer syntax let it stay that way. + */ + if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED)) + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + + cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason); + if (cond.bad()) return cond; + } + } + } + return EC_Normal; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * This method is passed a list of "preferred" transfer syntaxes. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes( + T_ASC_Parameters * params, + const char* transferSyntaxes[], int transferSyntaxCount, + T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT) +{ + OFCondition cond = EC_Normal; + /* + ** Accept in the order "least wanted" to "most wanted" transfer + ** syntax. Accepting a transfer syntax will override previously + ** accepted transfer syntaxes. + */ + for (int i = transferSyntaxCount - 1; i >= 0; i--) + { + cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole); + if (cond.bad()) return cond; + } + return cond; +} + + + +namespace Orthanc +{ + namespace Internals + { + /** + * EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0 + * (dcmAllStorageSOPClassUIDs). + * + * an array of const strings containing all known Storage SOP + * Classes that fit into the conventional + * PATIENT-STUDY-SERIES-INSTANCE information model, + * i.e. everything a Storage SCP might want to store in a PACS. + * Special cases such as hanging protocol storage or the Storage + * SOP Class are not included in this list. + * + * THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED + * ONES AND IS LARGER THAN 64 ENTRIES. + */ + + const char* orthancStorageSOPClassUIDs[] = + { + UID_AmbulatoryECGWaveformStorage, + UID_ArterialPulseWaveformStorage, + UID_AutorefractionMeasurementsStorage, + UID_BasicStructuredDisplayStorage, + UID_BasicTextSRStorage, + UID_BasicVoiceAudioWaveformStorage, + UID_BlendingSoftcopyPresentationStateStorage, + UID_BreastTomosynthesisImageStorage, + UID_CardiacElectrophysiologyWaveformStorage, + UID_ChestCADSRStorage, + UID_ColonCADSRStorage, + UID_ColorSoftcopyPresentationStateStorage, + UID_ComprehensiveSRStorage, + UID_ComputedRadiographyImageStorage, + UID_CTImageStorage, + UID_DeformableSpatialRegistrationStorage, + UID_DigitalIntraOralXRayImageStorageForPresentation, + UID_DigitalIntraOralXRayImageStorageForProcessing, + UID_DigitalMammographyXRayImageStorageForPresentation, + UID_DigitalMammographyXRayImageStorageForProcessing, + UID_DigitalXRayImageStorageForPresentation, + UID_DigitalXRayImageStorageForProcessing, + UID_EncapsulatedCDAStorage, + UID_EncapsulatedPDFStorage, + UID_EnhancedCTImageStorage, + UID_EnhancedMRColorImageStorage, + UID_EnhancedMRImageStorage, + UID_EnhancedPETImageStorage, + UID_EnhancedSRStorage, + UID_EnhancedUSVolumeStorage, + UID_EnhancedXAImageStorage, + UID_EnhancedXRFImageStorage, + UID_GeneralAudioWaveformStorage, + UID_GeneralECGWaveformStorage, + UID_GenericImplantTemplateStorage, + UID_GrayscaleSoftcopyPresentationStateStorage, + UID_HemodynamicWaveformStorage, + UID_ImplantAssemblyTemplateStorage, + UID_ImplantationPlanSRDocumentStorage, + UID_ImplantTemplateGroupStorage, + UID_IntraocularLensCalculationsStorage, + UID_KeratometryMeasurementsStorage, + UID_KeyObjectSelectionDocumentStorage, + UID_LensometryMeasurementsStorage, + UID_MacularGridThicknessAndVolumeReportStorage, + UID_MammographyCADSRStorage, + UID_MRImageStorage, + UID_MRSpectroscopyStorage, + UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage, + UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage, + UID_MultiframeSingleBitSecondaryCaptureImageStorage, + UID_MultiframeTrueColorSecondaryCaptureImageStorage, + UID_NuclearMedicineImageStorage, + UID_OphthalmicAxialMeasurementsStorage, + UID_OphthalmicPhotography16BitImageStorage, + UID_OphthalmicPhotography8BitImageStorage, + UID_OphthalmicTomographyImageStorage, + UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage, + UID_PositronEmissionTomographyImageStorage, + UID_ProcedureLogStorage, + UID_PseudoColorSoftcopyPresentationStateStorage, + UID_RawDataStorage, + UID_RealWorldValueMappingStorage, + UID_RespiratoryWaveformStorage, + UID_RTBeamsTreatmentRecordStorage, + UID_RTBrachyTreatmentRecordStorage, + UID_RTDoseStorage, + UID_RTImageStorage, + UID_RTIonBeamsTreatmentRecordStorage, + UID_RTIonPlanStorage, + UID_RTPlanStorage, + UID_RTStructureSetStorage, + UID_RTTreatmentSummaryRecordStorage, + UID_SecondaryCaptureImageStorage, + UID_SegmentationStorage, + UID_SpatialFiducialsStorage, + UID_SpatialRegistrationStorage, + UID_SpectaclePrescriptionReportStorage, + UID_StereometricRelationshipStorage, + UID_SubjectiveRefractionMeasurementsStorage, + UID_SurfaceSegmentationStorage, + UID_TwelveLeadECGWaveformStorage, + UID_UltrasoundImageStorage, + UID_UltrasoundMultiframeImageStorage, + UID_VideoEndoscopicImageStorage, + UID_VideoMicroscopicImageStorage, + UID_VideoPhotographicImageStorage, + UID_VisualAcuityMeasurementsStorage, + UID_VLEndoscopicImageStorage, + UID_VLMicroscopicImageStorage, + UID_VLPhotographicImageStorage, + UID_VLSlideCoordinatesMicroscopicImageStorage, + UID_VLWholeSlideMicroscopyImageStorage, + UID_XAXRFGrayscaleSoftcopyPresentationStateStorage, + UID_XRay3DAngiographicImageStorage, + UID_XRay3DCraniofacialImageStorage, + UID_XRayAngiographicImageStorage, + UID_XRayRadiationDoseSRStorage, + UID_XRayRadiofluoroscopicImageStorage, + // retired + UID_RETIRED_HardcopyColorImageStorage, + UID_RETIRED_HardcopyGrayscaleImageStorage, + UID_RETIRED_NuclearMedicineImageStorage, + UID_RETIRED_StandaloneCurveStorage, + UID_RETIRED_StandaloneModalityLUTStorage, + UID_RETIRED_StandaloneOverlayStorage, + UID_RETIRED_StandalonePETCurveStorage, + UID_RETIRED_StandaloneVOILUTStorage, + UID_RETIRED_StoredPrintStorage, + UID_RETIRED_UltrasoundImageStorage, + UID_RETIRED_UltrasoundMultiframeImageStorage, + UID_RETIRED_VLImageStorage, + UID_RETIRED_VLMultiFrameImageStorage, + UID_RETIRED_XRayAngiographicBiPlaneImageStorage, + // draft + UID_DRAFT_SRAudioStorage, + UID_DRAFT_SRComprehensiveStorage, + UID_DRAFT_SRDetailStorage, + UID_DRAFT_SRTextStorage, + UID_DRAFT_WaveformStorage, + UID_DRAFT_RTBeamsDeliveryInstructionStorage, + NULL + }; + + const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1; + + + + OFCondition AssociationCleanup(T_ASC_Association *assoc) + { + OFCondition cond = ASC_dropSCPAssociation(assoc); + if (cond.bad()) + { + LOG(FATAL) << cond.text(); + return cond; + } + + cond = ASC_destroyAssociation(&assoc); + if (cond.bad()) + { + LOG(FATAL) << cond.text(); + return cond; + } + + return cond; + } + + + + CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net) + { + DcmAssociationConfiguration asccfg; + char buf[BUFSIZ]; + T_ASC_Association *assoc; + OFCondition cond; + 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, + /*opt_secureConnection*/ OFFalse, + DUL_NOBLOCK, 1); + + if (cond == DUL_NOASSOCIATIONREQUEST) + { + // Timeout + AssociationCleanup(assoc); + return NULL; + } + + // if some kind of error occured, take care of it + if (cond.bad()) + { + LOG(ERROR) << "Receiving Association failed: " << cond.text(); + // no matter what kind of error occurred, we need to do a cleanup + AssociationCleanup(assoc); + return NULL; + } + + // Retrieve the AET and the IP address of the remote modality + std::string remoteAet; + std::string remoteIp; + std::string calledAet; + + { + DIC_AE remoteAet_C; + DIC_AE calledAet_C; + DIC_AE remoteIp_C; + DIC_AE calledIP_C; + if (ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() || + ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad()) + { + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_NOREASON + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C)); + remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C)); + calledAet = (/*OFSTRING_GUARD*/(calledAet_C)); + } + + LOG(INFO) << "Association Received from AET " << remoteAet + << " 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); + } + + 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); + } + + 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); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) + { + transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); + transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); + } + + 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); + } + + 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; + } + + /* the array of Storage SOP Class UIDs comes from dcuid.h */ + cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &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()); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + } + + /* set our app title */ + ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); + + /* acknowledge or reject this association */ + cond = ASC_getApplicationContextName(assoc->params, buf); + if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0) + { + /* reject: the application context name is not supported */ + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED + }; + + LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf; + cond = ASC_rejectAssociation(assoc, &rej); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + } + AssociationCleanup(assoc); + return NULL; + } + + /* check the AETs */ + if (!server.IsMyAETitle(calledAet)) + { + LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")"; + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + if (server.HasApplicationEntityFilter() && + !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet)) + { + LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp; + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + if (opt_rejectWithoutImplementationUID && + strlen(assoc->params->theirImplementationClassUID) == 0) + { + /* reject: the no implementation Class UID provided */ + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_NOREASON + }; + + LOG(INFO) << "Association Rejected: No Implementation Class UID provided"; + cond = ASC_rejectAssociation(assoc, &rej); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + } + AssociationCleanup(assoc); + return NULL; + } + + { + cond = ASC_acknowledgeAssociation(assoc); + if (cond.bad()) + { + LOG(ERROR) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"; + if (ASC_countAcceptedPresentationContexts(assoc->params) == 0) + LOG(INFO) << " (but no valid presentation contexts)"; + } + + IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; + return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter); + } + + + CommandDispatcher::CommandDispatcher(const DicomServer& server, + T_ASC_Association* assoc, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + IApplicationEntityFilter* filter) : + server_(server), + assoc_(assoc), + remoteIp_(remoteIp), + remoteAet_(remoteAet), + calledAet_(calledAet), + filter_(filter) + { + associationTimeout_ = server.GetAssociationTimeout(); + elapsedTimeSinceLastCommand_ = 0; + } + + + CommandDispatcher::~CommandDispatcher() + { + try + { + AssociationCleanup(assoc_); + } + catch (...) + { + LOG(ERROR) << "Some association was not cleanly aborted"; + } + } + + + bool CommandDispatcher::Step() + /* + * This function receives DIMSE commmands over the network connection + * and handles these commands correspondingly. Note that in case of + * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed. + */ + { + bool finished = false; + + // receive a DIMSE command over the network, with a timeout of 1 second + DcmDataset *statusDetail = NULL; + T_ASC_PresentationContextID presID = 0; + T_DIMSE_Message msg; + + OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail); + elapsedTimeSinceLastCommand_++; + + // if the command which was received has extra status + // detail information, dump this information + if (statusDetail != NULL) + { + //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail)); + delete statusDetail; + } + + if (cond == DIMSE_OUTOFRESOURCES) + { + finished = true; + } + else if (cond == DIMSE_NODATAAVAILABLE) + { + // Timeout due to DIMSE_NONBLOCKING + if (associationTimeout_ != 0 && + elapsedTimeSinceLastCommand_ >= associationTimeout_) + { + // This timeout is actually a association timeout + finished = true; + } + } + else if (cond == EC_Normal) + { + // Reset the association timeout counter + elapsedTimeSinceLastCommand_ = 0; + + // Convert the type of request to Orthanc's internal type + bool supported = false; + DicomRequestType request; + switch (msg.CommandField) + { + case DIMSE_C_ECHO_RQ: + request = DicomRequestType_Echo; + supported = true; + break; + + case DIMSE_C_STORE_RQ: + request = DicomRequestType_Store; + supported = true; + break; + + case DIMSE_C_MOVE_RQ: + request = DicomRequestType_Move; + supported = true; + break; + + case DIMSE_C_FIND_RQ: + request = DicomRequestType_Find; + supported = true; + break; + + default: + // we cannot handle this kind of message + cond = DIMSE_BADCOMMANDTYPE; + LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; + break; + } + + + // Check whether this request is allowed by the security filter + if (supported && + filter_ != NULL && + !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request)) + { + LOG(WARNING) << "Rejected " << EnumerationToString(request) + << " request from remote DICOM modality with AET \"" + << remoteAet_ << "\" and hostname \"" << remoteIp_ << "\""; + cond = DIMSE_ILLEGALASSOCIATION; + supported = false; + finished = true; + } + + // in case we received a supported message, process this command + if (supported) + { + // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer + cond = DIMSE_BADCOMMANDTYPE; + + switch (request) + { + case DicomRequestType_Echo: + cond = EchoScp(assoc_, &msg, presID); + break; + + case DicomRequestType_Store: + if (server_.HasStoreRequestHandlerFactory()) // Should always be true + { + std::auto_ptr<IStoreRequestHandler> handler + (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); + + if (handler.get() != NULL) + { + cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_); + } + } + break; + + case DicomRequestType_Move: + if (server_.HasMoveRequestHandlerFactory()) // Should always be true + { + std::auto_ptr<IMoveRequestHandler> handler + (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); + + if (handler.get() != NULL) + { + cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_); + } + } + break; + + case DicomRequestType_Find: + if (server_.HasFindRequestHandlerFactory() || // Should always be true + server_.HasWorklistRequestHandlerFactory()) + { + std::auto_ptr<IFindRequestHandler> findHandler; + if (server_.HasFindRequestHandlerFactory()) + { + findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); + } + + std::auto_ptr<IWorklistRequestHandler> worklistHandler; + if (server_.HasWorklistRequestHandlerFactory()) + { + worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler()); + } + + cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(), + findHandler.get(), worklistHandler.get(), + remoteIp_, remoteAet_, calledAet_); + } + break; + + default: + // Should never happen + break; + } + } + } + else + { + // Bad status, which indicates the closing of the connection by + // the peer or a network error + finished = true; + + LOG(INFO) << cond.text(); + } + + if (finished) + { + if (cond == DUL_PEERREQUESTEDRELEASE) + { + LOG(INFO) << "Association Release"; + ASC_acknowledgeRelease(assoc_); + } + else if (cond == DUL_PEERABORTEDASSOCIATION) + { + LOG(INFO) << "Association Aborted"; + } + else + { + OFString temp_str; + LOG(INFO) << "DIMSE failure (aborting association): " << cond.text(); + /* some kind of error so abort the association */ + ASC_abortAssociation(assoc_); + } + } + + return !finished; + } + + + OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) + { + OFString temp_str; + LOG(INFO) << "Received Echo Request"; + //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID)); + + /* the echo succeeded !! */ + OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL); + if (cond.bad()) + { + LOG(ERROR) << "Echo SCP Failed: " << cond.text(); + } + return cond; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,79 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../DicomServer.h" +#include "../../MultiThreading/IRunnableBySteps.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition AssociationCleanup(T_ASC_Association *assoc); + + class CommandDispatcher : public IRunnableBySteps + { + private: + uint32_t associationTimeout_; + uint32_t elapsedTimeSinceLastCommand_; + const DicomServer& server_; + T_ASC_Association* assoc_; + std::string remoteIp_; + std::string remoteAet_; + std::string calledAet_; + IApplicationEntityFilter* filter_; + + public: + CommandDispatcher(const DicomServer& server, + T_ASC_Association* assoc, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + IApplicationEntityFilter* filter); + + virtual ~CommandDispatcher(); + + 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); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/FindScp.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,348 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + + +#include "../../PrecompiledHeaders.h" +#include "FindScp.h" + +#include "../../DicomParsing/FromDcmtkBridge.h" +#include "../../DicomParsing/ToDcmtkBridge.h" +#include "../../Logging.h" +#include "../../OrthancException.h" + +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcdeftag.h> + + + +/** + * The function below is extracted from DCMTK 3.6.0, cf. file + * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc". + **/ + +static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, + const DcmTagKey &sequenceTagKey) +// Date : May 3, 2005 +// Author : Thomas Wilkens +// Task : This function performs a check on a sequence attribute in the given dataset. At two different places +// in the definition of the DICOM worklist management service, a sequence attribute with a return type +// of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes +// specifies that in case a sequence item is present, then these two attributes must be existent and +// must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.) +// In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass +// and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what +// this function does. +// Parameters : dataset - [in] Dataset in which the consistency of the sequence attribute shall be checked. +// sequenceTagKey - [in] DcmTagKey of the sequence attribute which shall be checked. +// Return Value : none. +{ + DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL; + + // in case the sequence attribute contains exactly one item with an empty + // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item + if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() && + ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 && + ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() && + referencedSOPClassUIDAttribute->getLength() == 0 && + ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() && + referencedSOPInstanceUIDAttribute->getLength() == 0 ) + { + DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) ); + delete item; + } +} + + + +namespace Orthanc +{ + namespace + { + struct FindScpData + { + DicomServer::IRemoteModalities* modalities_; + IFindRequestHandler* findHandler_; + IWorklistRequestHandler* worklistHandler_; + DicomFindAnswers answers_; + DcmDataset* lastRequest_; + const std::string* remoteIp_; + const std::string* remoteAet_; + const std::string* calledAet_; + + FindScpData() : answers_(false) + { + } + }; + + + + static void FixWorklistQuery(ParsedDicomFile& query) + { + // TODO: Check out + // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()" + // in DCMTK 3.6.0 + + DcmDataset* dataset = query.GetDcmtkObject().getDataset(); + HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence); + HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence); + } + + + + void FindScpCallback( + /* in */ + void *callbackData, + OFBool cancelled, + T_DIMSE_C_FindRQ *request, + DcmDataset *requestIdentifiers, + int responseCount, + /* out */ + T_DIMSE_C_FindRSP *response, + DcmDataset **responseIdentifiers, + DcmDataset **statusDetail) + { + bzero(response, sizeof(T_DIMSE_C_FindRSP)); + *statusDetail = NULL; + + std::string sopClassUid(request->AffectedSOPClassUID); + + FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData); + if (data.lastRequest_ == NULL) + { + bool ok = false; + + try + { + RemoteModalityParameters modality; + + /** + * Ensure that the remote modality is known to Orthanc for C-FIND requests. + **/ + + assert(data.modalities_ != NULL); + if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_)) + { + LOG(ERROR) << "Modality with AET \"" << *data.remoteAet_ + << "\" is not defined in the \"DicomModalities\" configuration option"; + throw OrthancException(ErrorCode_UnknownModality); + } + + + if (sopClassUid == UID_FINDModalityWorklistInformationModel) + { + data.answers_.SetWorklist(true); + + if (data.worklistHandler_ != NULL) + { + ParsedDicomFile query(*requestIdentifiers); + FixWorklistQuery(query); + + data.worklistHandler_->Handle(data.answers_, query, + *data.remoteIp_, *data.remoteAet_, + *data.calledAet_, modality.GetManufacturer()); + ok = true; + } + else + { + LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request"; + } + } + else + { + data.answers_.SetWorklist(false); + + if (data.findHandler_ != NULL) + { + std::list<DicomTag> sequencesToReturn; + + for (unsigned long i = 0; i < requestIdentifiers->card(); i++) + { + DcmElement* element = requestIdentifiers->getElement(i); + if (element && !element->isLeaf()) + { + const DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); + + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + if (sequence.card() != 0) + { + LOG(WARNING) << "Orthanc only supports sequence matching on worklists, " + << "ignoring C-FIND SCU constraint on tag (" << tag.Format() + << ") " << FromDcmtkBridge::GetTagName(*element); + } + + sequencesToReturn.push_back(tag); + } + } + + DicomMap input; + FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); + + data.findHandler_->Handle(data.answers_, input, sequencesToReturn, + *data.remoteIp_, *data.remoteAet_, + *data.calledAet_, modality.GetManufacturer()); + ok = true; + } + else + { + LOG(ERROR) << "No C-Find handler is installed, cannot handle this request"; + } + } + } + catch (OrthancException& e) + { + // Internal error! + LOG(ERROR) << "C-FIND request handler has failed: " << e.What(); + } + + if (!ok) + { + response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; + *responseIdentifiers = NULL; + return; + } + + data.lastRequest_ = requestIdentifiers; + } + else if (data.lastRequest_ != requestIdentifiers) + { + // Internal error! + response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; + *responseIdentifiers = NULL; + return; + } + + if (responseCount <= static_cast<int>(data.answers_.GetSize())) + { + // There are pending results that are still to be sent + response->DimseStatus = STATUS_Pending; + *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1); + } + else if (data.answers_.IsComplete()) + { + // Success: All the results have been sent + response->DimseStatus = STATUS_Success; + *responseIdentifiers = NULL; + } + else + { + // Success, but the results were too numerous and had to be cropped + LOG(WARNING) << "Too many results for an incoming C-FIND query"; + response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest; + *responseIdentifiers = NULL; + } + } + } + + + OFCondition Internals::findScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + DicomServer::IRemoteModalities& modalities, + IFindRequestHandler* findHandler, + IWorklistRequestHandler* worklistHandler, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + FindScpData data; + data.modalities_ = &modalities; + data.findHandler_ = findHandler; + data.worklistHandler_ = worklistHandler; + data.lastRequest_ = NULL; + data.remoteIp_ = &remoteIp; + data.remoteAet_ = &remoteAet; + data.calledAet_ = &calledAet; + + OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, + FindScpCallback, &data, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ 0); + + // if some error occured, dump corresponding information and remove the outfile if necessary + if (cond.bad()) + { + OFString temp_str; + LOG(ERROR) << "Find SCP Failed: " << cond.text(); + } + + return cond; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/FindScp.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../DicomServer.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition findScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + DicomServer::IRemoteModalities& modalities, + IFindRequestHandler* findHandler, // can be NULL + IWorklistRequestHandler* worklistHandler, // can be NULL + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/MoveScp.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,299 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../../PrecompiledHeaders.h" +#include "MoveScp.h" + +#include <memory> + +#include "../../DicomParsing/FromDcmtkBridge.h" +#include "../../DicomParsing/ToDcmtkBridge.h" +#include "../../Logging.h" +#include "../../OrthancException.h" + +#include <boost/lexical_cast.hpp> + + +/** + * Macro specifying whether to apply the patch suggested in issue 66: + * "Orthanc responses C-MOVE with zero Move Originator Message ID" + * https://bitbucket.org/sjodogne/orthanc/issues/66/ + **/ + +#define APPLY_FIX_ISSUE_66 1 + + +namespace Orthanc +{ + namespace + { + struct MoveScpData + { + std::string target_; + IMoveRequestHandler* handler_; + DcmDataset* lastRequest_; + unsigned int subOperationCount_; + unsigned int failureCount_; + unsigned int warningCount_; + std::auto_ptr<IMoveRequestIterator> iterator_; + const std::string* remoteIp_; + const std::string* remoteAet_; + const std::string* calledAet_; + }; + + +#if APPLY_FIX_ISSUE_66 != 1 + static uint16_t GetMessageId(const DicomMap& message) + { + /** + * Retrieve the Message ID (0000,0110) for this C-MOVE request, if + * any. If present, this Message ID will be stored in the Move + * Originator Message ID (0000,1031) field of the C-MOVE response. + * http://dicom.nema.org/dicom/2013/output/chtml/part07/chapter_E.html + **/ + + const DicomValue* value = message.TestAndGetValue(DICOM_TAG_MESSAGE_ID); + + if (value != NULL && + !value->IsNull() && + !value->IsBinary()) + { + try + { + int tmp = boost::lexical_cast<int>(value->GetContent()); + if (tmp >= 0 && tmp <= 0xffff) + { + return static_cast<uint16_t>(tmp); + } + } + catch (boost::bad_lexical_cast&) + { + LOG(WARNING) << "Cannot convert the Message ID (\"" << value->GetContent() + << "\") of an incoming C-MOVE request to an integer, assuming zero"; + } + } + + return 0; + } +#endif + + + void MoveScpCallback( + /* in */ + void *callbackData, + OFBool cancelled, + T_DIMSE_C_MoveRQ *request, + DcmDataset *requestIdentifiers, + int responseCount, + /* out */ + T_DIMSE_C_MoveRSP *response, + DcmDataset **responseIdentifiers, + DcmDataset **statusDetail) + { + bzero(response, sizeof(T_DIMSE_C_MoveRSP)); + *statusDetail = NULL; + *responseIdentifiers = NULL; + + MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData); + if (data.lastRequest_ == NULL) + { + DicomMap input; + FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); + + try + { +#if APPLY_FIX_ISSUE_66 == 1 + uint16_t messageId = request->MessageID; +#else + // The line below was the implementation for Orthanc <= 1.3.2 + uint16_t messageId = GetMessageId(input); +#endif + + data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_, + *data.calledAet_, messageId)); + + if (data.iterator_.get() == NULL) + { + // Internal error! + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + data.subOperationCount_ = data.iterator_->GetSubOperationCount(); + data.failureCount_ = 0; + data.warningCount_ = 0; + } + catch (OrthancException& e) + { + // Internal error! + LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + data.lastRequest_ = requestIdentifiers; + } + else if (data.lastRequest_ != requestIdentifiers) + { + // Internal error! + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + if (data.subOperationCount_ == 0) + { + response->DimseStatus = STATUS_Success; + } + else + { + IMoveRequestIterator::Status status; + + try + { + status = data.iterator_->DoNext(); + } + catch (OrthancException& e) + { + // Internal error! + LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + if (status == IMoveRequestIterator::Status_Failure) + { + data.failureCount_++; + } + else if (status == IMoveRequestIterator::Status_Warning) + { + data.warningCount_++; + } + + if (responseCount < static_cast<int>(data.subOperationCount_)) + { + response->DimseStatus = STATUS_Pending; + } + else + { + response->DimseStatus = STATUS_Success; + } + } + + response->NumberOfRemainingSubOperations = data.subOperationCount_ - responseCount; + response->NumberOfCompletedSubOperations = responseCount; + response->NumberOfFailedSubOperations = data.failureCount_; + response->NumberOfWarningSubOperations = data.warningCount_; + } + } + + + OFCondition Internals::moveScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IMoveRequestHandler& handler, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + MoveScpData data; + data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination); + data.lastRequest_ = NULL; + data.handler_ = &handler; + data.remoteIp_ = &remoteIp; + data.remoteAet_ = &remoteAet; + data.calledAet_ = &calledAet; + + OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, + MoveScpCallback, &data, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ 0); + + // if some error occured, dump corresponding information and remove the outfile if necessary + if (cond.bad()) + { + OFString temp_str; + LOG(ERROR) << "Move SCP Failed: " << cond.text(); + } + + return cond; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/MoveScp.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,52 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../IMoveRequestHandler.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition moveScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IMoveRequestHandler& handler, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/StoreScp.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,300 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../../PrecompiledHeaders.h" +#include "StoreScp.h" + +#include "../../DicomParsing/FromDcmtkBridge.h" +#include "../../DicomParsing/ToDcmtkBridge.h" +#include "../../OrthancException.h" +#include "../../Logging.h" + +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmnet/diutil.h> + + +namespace Orthanc +{ + namespace + { + struct StoreCallbackData + { + IStoreRequestHandler* handler; + const std::string* remoteIp; + const char* remoteAET; + const char* calledAET; + const char* modality; + const char* affectedSOPInstanceUID; + uint32_t messageID; + }; + + + static void + storeScpCallback( + void *callbackData, + T_DIMSE_StoreProgress *progress, + T_DIMSE_C_StoreRQ *req, + char * /*imageFileName*/, DcmDataset **imageDataSet, + T_DIMSE_C_StoreRSP *rsp, + DcmDataset **statusDetail) + /* + * This function.is used to indicate progress when storescp receives instance data over the + * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd) + * this function will store the data set which was received over the network to a file. + * Earlier calls to this function will simply cause some information to be dumped to stdout. + * + * Parameters: + * callbackData - [in] data for this callback function + * progress - [in] The state of progress. (identifies if this is the initial or final call + * to this function, or a call in between these two calls. + * req - [in] The original store request message. + * imageFileName - [in] The path to and name of the file the information shall be written to. + * imageDataSet - [in] The data set which shall be stored in the image file + * rsp - [inout] the C-STORE-RSP message (will be sent after the call to this function) + * statusDetail - [inout] This variable can be used to capture detailed information with regard to + * the status information which is captured in the status element (0000,0900). Note + * that this function does specify any such information, the pointer will be set to NULL. + */ + { + StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData); + + DIC_UI sopClass; + DIC_UI sopInstance; + + // if this is the final call of this function, save the data which was received to a file + // (note that we could also save the image somewhere else, put it in database, etc.) + if (progress->state == DIMSE_StoreEnd) + { + OFString tmpStr; + + // do not send status detail information + *statusDetail = NULL; + + // Concerning the following line: an appropriate status code is already set in the resp structure, + // it need not be success. For example, if the caller has already detected an out of resources problem + // then the status will reflect this. The callback function is still called to allow cleanup. + //rsp->DimseStatus = STATUS_Success; + + // we want to write the received information to a file only if this information + // is present and the options opt_bitPreserving and opt_ignore are not set. + if ((imageDataSet != NULL) && (*imageDataSet != NULL)) + { + DicomMap summary; + Json::Value dicomJson; + std::string buffer; + + try + { + std::set<DicomTag> ignoreTagLength; + + FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet); + FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet, ignoreTagLength); + + if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) + { + LOG(ERROR) << "cannot write DICOM file to memory"; + rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; + } + } + catch (...) + { + rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; + } + + // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond + // to those mentioned in the request. If not, set the status in the response message variable. + if (rsp->DimseStatus == STATUS_Success) + { + // which SOP class and SOP instance ? + if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse)) + { + //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName); + rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand; + } + else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0) + { + rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; + } + else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0) + { + rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; + } + else + { + try + { + cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); + } + catch (OrthancException& e) + { + rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; + + if (e.GetErrorCode() == ErrorCode_InexistentTag) + { + summary.LogMissingTagsForStore(); + } + else + { + LOG(ERROR) << "Exception while storing DICOM: " << e.What(); + } + } + } + } + } + } + } + } + +/* + * This function processes a DIMSE C-STORE-RQ commmand that was + * received over the network connection. + * + * Parameters: + * assoc - [in] The association (network connection to another DICOM application). + * msg - [in] The DIMSE C-STORE-RQ message that was received. + * presID - [in] The ID of the presentation context which was specified in the PDV which contained + * the DIMSE command. + */ + OFCondition Internals::storeScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IStoreRequestHandler& handler, + const std::string& remoteIp) + { + OFCondition cond = EC_Normal; + T_DIMSE_C_StoreRQ *req; + + // assign the actual information of the C-STORE-RQ command to a local variable + req = &msg->msg.CStoreRQ; + + // intialize some variables + StoreCallbackData data; + data.handler = &handler; + data.remoteIp = &remoteIp; + data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/); + if (data.modality == NULL) + data.modality = "UNKNOWN"; + + data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID; + data.messageID = req->MessageID; + if (assoc && assoc->params) + { + data.remoteAET = assoc->params->DULparams.callingAPTitle; + data.calledAET = assoc->params->DULparams.calledAPTitle; + } + else + { + data.remoteAET = ""; + data.calledAET = ""; + } + + DcmFileFormat dcmff; + + // store SourceApplicationEntityTitle in metaheader + if (assoc && assoc->params) + { + const char *aet = assoc->params->DULparams.callingAPTitle; + if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet); + } + + // define an address where the information which will be received over the network will be stored + DcmDataset *dset = dcmff.getDataset(); + + cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset, + storeScpCallback, &data, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ 0); + + // if some error occured, dump corresponding information and remove the outfile if necessary + if (cond.bad()) + { + OFString temp_str; + LOG(ERROR) << "Store SCP Failed: " << cond.text(); + } + + // return return value + return cond; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/StoreScp.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,50 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../IStoreRequestHandler.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition storeScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IStoreRequestHandler& handler, + const std::string& remoteIp); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,328 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "RemoteModalityParameters.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../SerializationToolbox.h" + +#include <boost/lexical_cast.hpp> +#include <stdexcept> + + +static const char* KEY_AET = "AET"; +static const char* KEY_ALLOW_ECHO = "AllowEcho"; +static const char* KEY_ALLOW_FIND = "AllowFind"; +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_HOST = "Host"; +static const char* KEY_MANUFACTURER = "Manufacturer"; +static const char* KEY_PORT = "Port"; + + +namespace Orthanc +{ + void RemoteModalityParameters::Clear() + { + aet_ = "ORTHANC"; + host_ = "127.0.0.1"; + port_ = 104; + manufacturer_ = ModalityManufacturer_Generic; + allowEcho_ = true; + allowStore_ = true; + allowFind_ = true; + allowMove_ = true; + allowGet_ = true; + } + + + RemoteModalityParameters::RemoteModalityParameters(const std::string& aet, + const std::string& host, + uint16_t port, + ModalityManufacturer manufacturer) + { + Clear(); + SetApplicationEntityTitle(aet); + SetHost(host); + SetPortNumber(port); + SetManufacturer(manufacturer); + } + + + static void CheckPortNumber(int value) + { + if (value <= 0 || + value >= 65535) + { + LOG(ERROR) << "A TCP port number must be in range [1..65534], but found: " << value; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static uint16_t ReadPortNumber(const Json::Value& value) + { + int tmp; + + switch (value.type()) + { + case Json::intValue: + case Json::uintValue: + tmp = value.asInt(); + break; + + case Json::stringValue: + try + { + tmp = boost::lexical_cast<int>(value.asString()); + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + break; + + default: + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckPortNumber(tmp); + return static_cast<uint16_t>(tmp); + } + + + void RemoteModalityParameters::SetPortNumber(uint16_t port) + { + CheckPortNumber(port); + port_ = port; + } + + + void RemoteModalityParameters::UnserializeArray(const Json::Value& serialized) + { + assert(serialized.type() == Json::arrayValue); + + if ((serialized.size() != 3 && + serialized.size() != 4) || + serialized[0].type() != Json::stringValue || + serialized[1].type() != Json::stringValue || + (serialized.size() == 4 && + serialized[3].type() != Json::stringValue)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + aet_ = serialized[0].asString(); + host_ = serialized[1].asString(); + port_ = ReadPortNumber(serialized[2]); + + if (serialized.size() == 4) + { + manufacturer_ = StringToModalityManufacturer(serialized[3].asString()); + } + else + { + manufacturer_ = ModalityManufacturer_Generic; + } + } + + + void RemoteModalityParameters::UnserializeObject(const Json::Value& serialized) + { + assert(serialized.type() == Json::objectValue); + + aet_ = SerializationToolbox::ReadString(serialized, KEY_AET); + host_ = SerializationToolbox::ReadString(serialized, KEY_HOST); + + if (serialized.isMember(KEY_PORT)) + { + port_ = ReadPortNumber(serialized[KEY_PORT]); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + if (serialized.isMember(KEY_MANUFACTURER)) + { + manufacturer_ = StringToModalityManufacturer + (SerializationToolbox::ReadString(serialized, KEY_MANUFACTURER)); + } + else + { + manufacturer_ = ModalityManufacturer_Generic; + } + + if (serialized.isMember(KEY_ALLOW_ECHO)) + { + allowEcho_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_ECHO); + } + + if (serialized.isMember(KEY_ALLOW_FIND)) + { + allowFind_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_FIND); + } + + if (serialized.isMember(KEY_ALLOW_STORE)) + { + allowStore_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_STORE); + } + + if (serialized.isMember(KEY_ALLOW_GET)) + { + allowGet_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_GET); + } + + if (serialized.isMember(KEY_ALLOW_MOVE)) + { + allowMove_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_MOVE); + } + } + + + bool RemoteModalityParameters::IsRequestAllowed(DicomRequestType type) const + { + switch (type) + { + case DicomRequestType_Echo: + return allowEcho_; + + case DicomRequestType_Find: + return allowFind_; + + case DicomRequestType_Get: + return allowGet_; + + case DicomRequestType_Move: + return allowMove_; + + case DicomRequestType_Store: + return allowStore_; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + void RemoteModalityParameters::SetRequestAllowed(DicomRequestType type, + bool allowed) + { + switch (type) + { + case DicomRequestType_Echo: + allowEcho_ = allowed; + break; + + case DicomRequestType_Find: + allowFind_ = allowed; + break; + + case DicomRequestType_Get: + allowGet_ = allowed; + break; + + case DicomRequestType_Move: + allowMove_ = allowed; + break; + + case DicomRequestType_Store: + allowStore_ = allowed; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool RemoteModalityParameters::IsAdvancedFormatNeeded() const + { + return (!allowEcho_ || + !allowStore_ || + !allowFind_ || + !allowGet_ || + !allowMove_); + } + + + void RemoteModalityParameters::Serialize(Json::Value& target, + bool forceAdvancedFormat) const + { + if (forceAdvancedFormat || + IsAdvancedFormatNeeded()) + { + target = Json::objectValue; + target[KEY_AET] = aet_; + target[KEY_HOST] = host_; + target[KEY_PORT] = port_; + target[KEY_MANUFACTURER] = EnumerationToString(manufacturer_); + target[KEY_ALLOW_ECHO] = allowEcho_; + target[KEY_ALLOW_STORE] = allowStore_; + target[KEY_ALLOW_FIND] = allowFind_; + target[KEY_ALLOW_GET] = allowGet_; + target[KEY_ALLOW_MOVE] = allowMove_; + } + else + { + target = Json::arrayValue; + target.append(GetApplicationEntityTitle()); + target.append(GetHost()); + target.append(GetPortNumber()); + target.append(EnumerationToString(GetManufacturer())); + } + } + + + void RemoteModalityParameters::Unserialize(const Json::Value& serialized) + { + Clear(); + + switch (serialized.type()) + { + case Json::objectValue: + UnserializeObject(serialized); + break; + + case Json::arrayValue: + UnserializeArray(serialized); + break; + + default: + throw OrthancException(ErrorCode_BadFileFormat); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,133 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <stdint.h> +#include <string> +#include <json/json.h> + +namespace Orthanc +{ + class RemoteModalityParameters + { + private: + std::string aet_; + std::string host_; + uint16_t port_; + ModalityManufacturer manufacturer_; + bool allowEcho_; + bool allowStore_; + bool allowFind_; + bool allowMove_; + bool allowGet_; + + void Clear(); + + void UnserializeArray(const Json::Value& serialized); + + void UnserializeObject(const Json::Value& serialized); + + public: + RemoteModalityParameters() + { + Clear(); + } + + RemoteModalityParameters(const Json::Value& serialized) + { + Unserialize(serialized); + } + + RemoteModalityParameters(const std::string& aet, + const std::string& host, + uint16_t port, + ModalityManufacturer manufacturer); + + const std::string& GetApplicationEntityTitle() const + { + return aet_; + } + + void SetApplicationEntityTitle(const std::string& aet) + { + aet_ = aet; + } + + const std::string& GetHost() const + { + return host_; + } + + void SetHost(const std::string& host) + { + host_ = host; + } + + uint16_t GetPortNumber() const + { + return port_; + } + + void SetPortNumber(uint16_t port); + + ModalityManufacturer GetManufacturer() const + { + return manufacturer_; + } + + void SetManufacturer(ModalityManufacturer manufacturer) + { + manufacturer_ = manufacturer; + } + + void SetManufacturer(const std::string& manufacturer) + { + manufacturer_ = StringToModalityManufacturer(manufacturer); + } + + bool IsRequestAllowed(DicomRequestType type) const; + + void SetRequestAllowed(DicomRequestType type, + bool allowed); + + void Unserialize(const Json::Value& modality); + + bool IsAdvancedFormatNeeded() const; + + void Serialize(Json::Value& target, + bool forceAdvancedFormat) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,134 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "TimeoutDicomConnectionManager.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + static boost::posix_time::ptime GetNow() + { + return boost::posix_time::microsec_clock::universal_time(); + } + + class TimeoutDicomConnectionManager::Resource : public IDicomConnectionManager::IResource + { + private: + TimeoutDicomConnectionManager& that_; + + public: + Resource(TimeoutDicomConnectionManager& that) : + that_(that) + { + if (that_.connection_.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + ~Resource() + { + that_.Touch(); + } + + DicomUserConnection& GetConnection() + { + assert(that_.connection_.get() != NULL); + return *that_.connection_; + } + }; + + + void TimeoutDicomConnectionManager::Touch() + { + lastUse_ = GetNow(); + } + + + void TimeoutDicomConnectionManager::CheckTimeoutInternal() + { + if (connection_.get() != NULL && + (GetNow() - lastUse_) >= timeout_) + { + Close(); + } + } + + + void TimeoutDicomConnectionManager::SetTimeout(unsigned int timeout) + { + timeout_ = boost::posix_time::milliseconds(timeout); + CheckTimeoutInternal(); + } + + + unsigned int TimeoutDicomConnectionManager::GetTimeout() + { + return timeout_.total_milliseconds(); + } + + + void TimeoutDicomConnectionManager::Close() + { + if (connection_.get() != NULL) + { + LOG(INFO) << "Closing inactive DICOM association with modality: " + << connection_->GetRemoteApplicationEntityTitle(); + + connection_.reset(NULL); + } + } + + + void TimeoutDicomConnectionManager::CheckTimeout() + { + CheckTimeoutInternal(); + } + + + IDicomConnectionManager::IResource* + TimeoutDicomConnectionManager::AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote) + { + if (connection_.get() == NULL || + !connection_->IsSameAssociation(localAet, remote)) + { + connection_.reset(new DicomUserConnection(localAet, remote)); + } + + return new Resource(*this); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,102 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IDicomConnectionManager.h" + +#if ORTHANC_ENABLE_DCMTK_NETWORKING == 0 + +namespace Orthanc +{ + class TimeoutDicomConnectionManager : public IDicomConnectionManager + { + public: + void SetTimeout(unsigned int timeout) + { + } + + unsigned int GetTimeout() + { + return 0; + } + + void Close() + { + } + + void CheckTimeout() + { + } + }; +} + +#else + +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace Orthanc +{ + class TimeoutDicomConnectionManager : public IDicomConnectionManager + { + private: + class Resource; + + std::auto_ptr<DicomUserConnection> connection_; + boost::posix_time::ptime lastUse_; + boost::posix_time::time_duration timeout_; + + void Touch(); + + void CheckTimeoutInternal(); + + public: + TimeoutDicomConnectionManager() : + timeout_(boost::posix_time::milliseconds(1000)) + { + } + + void SetTimeout(unsigned int timeout); + + unsigned int GetTimeout(); + + void Close(); + + void CheckTimeout(); + + virtual IResource* AcquireConnection(const std::string& localAet, + const RemoteModalityParameters& remote); + }; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomDirWriter.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,582 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + + +/*** + + Validation: + + # sudo apt-get install dicom3tools + # dciodvfy DICOMDIR 2>&1 | less + # dcentvfy DICOMDIR 2>&1 | less + + http://www.dclunie.com/dicom3tools/dciodvfy.html + + DICOMDIR viewer working with Wine under Linux: + http://www.microdicom.com/ + + ***/ + + +#include "../PrecompiledHeaders.h" +#include "DicomDirWriter.h" + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../TemporaryFile.h" +#include "../Toolbox.h" +#include "../SystemToolbox.h" + +#include <dcmtk/dcmdata/dcdicdir.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcddirif.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcsequen.h> +#include <dcmtk/dcmdata/dcostrmf.h> +#include "dcmtk/dcmdata/dcvrda.h" /* for class DcmDate */ +#include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ + +#include <memory> + +namespace Orthanc +{ + class DicomDirWriter::PImpl + { + private: + bool utc_; + std::string fileSetId_; + bool extendedSopClass_; + TemporaryFile file_; + std::auto_ptr<DcmDicomDir> dir_; + + typedef std::pair<ResourceType, std::string> IndexKey; + typedef std::map<IndexKey, DcmDirectoryRecord* > Index; + Index index_; + + + DcmDicomDir& GetDicomDir() + { + if (dir_.get() == NULL) + { + dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), + fileSetId_.c_str())); + //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8)); + } + + return *dir_; + } + + + DcmDirectoryRecord& GetRoot() + { + return GetDicomDir().getRootRecord(); + } + + + static bool GetUtf8TagValue(std::string& result, + DcmItem& source, + Encoding encoding, + const DcmTagKey& key) + { + DcmElement* element = NULL; + result.clear(); + + if (source.findAndGetElement(key, element).good()) + { + char* s = NULL; + if (element->isLeaf() && + element->getString(s).good()) + { + if (s != NULL) + { + result = Toolbox::ConvertToUtf8(s, encoding); + } + + return true; + } + } + + return false; + } + + + static void SetTagValue(DcmDirectoryRecord& target, + const DcmTagKey& key, + const std::string& valueUtf8) + { + std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii); + + if (!target.putAndInsertString(key, s.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + + static bool CopyString(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key, + bool optional, + bool copyEmpty) + { + if (optional && + !source.tagExistsWithValue(key) && + !(copyEmpty && source.tagExists(key))) + { + return false; + } + + std::string value; + bool found = GetUtf8TagValue(value, source, encoding, key); + + if (!found) + { + // We don't raise an exception if "!optional", even if this + // results in an invalid DICOM file + value.clear(); + } + + SetTagValue(target, key, value); + return found; + } + + + static void CopyStringType1(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, false, false); + } + + static void CopyStringType1C(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, true, false); + } + + static void CopyStringType2(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, false, true); + } + + static void CopyStringType3(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, true, true); + } + + + public: + PImpl() : + utc_(true), // By default, use UTC (universal time, not local time) + fileSetId_("ORTHANC_MEDIA"), + extendedSopClass_(false) + { + } + + bool IsUtcUsed() const + { + return utc_; + } + + + void SetUtcUsed(bool utc) + { + utc_ = utc; + } + + void EnableExtendedSopClass(bool enable) + { + if (enable) + { + LOG(WARNING) << "Generating a DICOMDIR with type 3 attributes, " + << "which leads to an Extended SOP Class"; + } + + extendedSopClass_ = enable; + } + + bool IsExtendedSopClass() const + { + return extendedSopClass_; + } + + void FillPatient(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildPatientRecord()" + + CopyStringType1C(record, dicom, encoding, DCM_PatientID); + CopyStringType2(record, dicom, encoding, DCM_PatientName); + } + + void FillStudy(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildStudyRecord()" + + std::string nowDate, nowTime; + SystemToolbox::GetNowDicom(nowDate, nowTime, utc_); + + std::string studyDate; + if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate)) + { + studyDate = nowDate; + } + + std::string studyTime; + if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime)) + { + studyTime = nowTime; + } + + /* copy attribute values from dataset to study record */ + SetTagValue(record, DCM_StudyDate, studyDate); + SetTagValue(record, DCM_StudyTime, studyTime); + CopyStringType2(record, dicom, encoding, DCM_StudyDescription); + CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID); + /* use type 1C instead of 1 in order to avoid unwanted overwriting */ + CopyStringType1C(record, dicom, encoding, DCM_StudyID); + CopyStringType2(record, dicom, encoding, DCM_AccessionNumber); + } + + void FillSeries(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildSeriesRecord()" + + /* copy attribute values from dataset to series record */ + CopyStringType1(record, dicom, encoding, DCM_Modality); + CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID); + /* use type 1C instead of 1 in order to avoid unwanted overwriting */ + CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber); + + // Add extended (non-standard) type 3 tags, those are not generated by DCMTK + // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part02/sect_7.3.html + // https://groups.google.com/d/msg/orthanc-users/Y7LOvZMDeoc/9cp3kDgxAwAJ + if (extendedSopClass_) + { + CopyStringType3(record, dicom, encoding, DCM_SeriesDescription); + } + } + + void FillInstance(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding, + DcmMetaInfo& metaInfo, + const char* path) + { + // cf. "DicomDirInterface::buildImageRecord()" + + /* copy attribute values from dataset to image record */ + CopyStringType1(record, dicom, encoding, DCM_InstanceNumber); + //CopyElementType1C(record, dicom, encoding, DCM_ImageType); + + // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record); + + std::string sopClassUid, sopInstanceUid, transferSyntaxUid; + if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) || + !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) || + !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + SetTagValue(record, DCM_ReferencedFileID, path); + SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid); + SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid); + SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid); + } + + + + bool CreateResource(DcmDirectoryRecord*& target, + ResourceType level, + ParsedDicomFile& dicom, + const char* filename, + const char* path) + { + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + Encoding encoding = dicom.GetEncoding(); + + bool found; + std::string id; + E_DirRecType type; + + switch (level) + { + case ResourceType_Patient: + found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID); + type = ERT_Patient; + break; + + case ResourceType_Study: + found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID); + type = ERT_Study; + break; + + case ResourceType_Series: + found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID); + type = ERT_Series; + break; + + case ResourceType_Instance: + found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID); + type = ERT_Image; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (!found) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + IndexKey key = std::make_pair(level, std::string(id.c_str())); + Index::iterator it = index_.find(key); + + if (it != index_.end()) + { + target = it->second; + return false; // Already existing + } + + std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); + + switch (level) + { + case ResourceType_Patient: + FillPatient(*record, dataset, encoding); + break; + + case ResourceType_Study: + FillStudy(*record, dataset, encoding); + break; + + case ResourceType_Series: + FillSeries(*record, dataset, encoding); + break; + + case ResourceType_Instance: + FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet); + + target = record.get(); + GetRoot().insertSub(record.release()); + index_[key] = target; + + return true; // Newly created + } + + void Read(std::string& s) + { + if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, + EET_UndefinedLength /*encodingType*/, + EGL_withoutGL /*groupLength*/).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + + file_.Read(s); + } + + void SetFileSetId(const std::string& id) + { + dir_.reset(NULL); + fileSetId_ = id; + } + }; + + + DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl) + { + } + + void DicomDirWriter::SetUtcUsed(bool utc) + { + pimpl_->SetUtcUsed(utc); + } + + bool DicomDirWriter::IsUtcUsed() const + { + return pimpl_->IsUtcUsed(); + } + + void DicomDirWriter::SetFileSetId(const std::string& id) + { + pimpl_->SetFileSetId(id); + } + + void DicomDirWriter::Add(const std::string& directory, + const std::string& filename, + ParsedDicomFile& dicom) + { + std::string path; + if (directory.empty()) + { + path = filename; + } + else + { + if (directory[directory.length() - 1] == '/' || + directory[directory.length() - 1] == '\\') + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + path = directory + '\\' + filename; + } + + DcmDirectoryRecord* instance; + bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str()); + if (isNewInstance) + { + DcmDirectoryRecord* series; + bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL); + series->insertSub(instance); + + if (isNewSeries) + { + DcmDirectoryRecord* study; + bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL); + study->insertSub(series); + + if (isNewStudy) + { + DcmDirectoryRecord* patient; + pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL); + patient->insertSub(study); + } + } + } + } + + void DicomDirWriter::Encode(std::string& target) + { + pimpl_->Read(target); + } + + + void DicomDirWriter::EnableExtendedSopClass(bool enable) + { + pimpl_->EnableExtendedSopClass(enable); + } + + + bool DicomDirWriter::IsExtendedSopClass() const + { + return pimpl_->IsExtendedSopClass(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomDirWriter.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,68 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ParsedDicomFile.h" + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class DicomDirWriter : public boost::noncopyable + { + private: + class PImpl; + boost::shared_ptr<PImpl> pimpl_; + + public: + DicomDirWriter(); + + void SetUtcUsed(bool utc); + + bool IsUtcUsed() const; + + void SetFileSetId(const std::string& id); + + void Add(const std::string& directory, + const std::string& filename, + ParsedDicomFile& dicom); + + void Encode(std::string& target); + + void EnableExtendedSopClass(bool enable); + + bool IsExtendedSopClass() const; + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomModification.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1415 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomModification.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../SerializationToolbox.h" +#include "FromDcmtkBridge.h" +#include "ITagVisitor.h" + +#include <memory> // For std::auto_ptr + + +static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 = + "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; + +static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c = + "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile"; + +namespace Orthanc +{ + class DicomModification::RelationshipsVisitor : public ITagVisitor + { + private: + DicomModification& that_; + + bool IsEnabled(const DicomTag& tag) const + { + return (!that_.IsCleared(tag) && + !that_.IsRemoved(tag) && + !that_.IsReplaced(tag)); + } + + void RemoveIfEnabled(ParsedDicomFile& dicom, + const DicomTag& tag) const + { + if (IsEnabled(tag)) + { + dicom.Remove(tag); + } + } + + + public: + RelationshipsVisitor(DicomModification& that) : + that_(that) + { + } + + virtual void VisitUnknown(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr) + { + } + + virtual void VisitBinary(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const void* data, + size_t size) + { + } + + virtual void VisitInteger(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + int64_t value) + { + } + + virtual void VisitDouble(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + double value) + { + } + + virtual void VisitAttribute(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const DicomTag& value) + { + } + + virtual Action VisitString(std::string& newValue, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::string& value) + { + if (!IsEnabled(tag)) + { + return Action_None; + } + else if (tag == DICOM_TAG_FRAME_OF_REFERENCE_UID || + tag == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID || + tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID || + tag == DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID) + { + newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Instance); + return Action_Replace; + } + else if (parentTags.size() == 1 && + parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && + tag == DICOM_TAG_STUDY_INSTANCE_UID) + { + newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study); + return Action_Replace; + } + else if (parentTags.size() == 2 && + parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && + parentTags[1] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE && + tag == DICOM_TAG_SERIES_INSTANCE_UID) + { + newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); + return Action_Replace; + } + else + { + return Action_None; + } + } + + void RemoveRelationships(ParsedDicomFile& dicom) const + { + // Sequences containing the UID relationships + RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_IMAGE_SEQUENCE); + RemoveIfEnabled(dicom, DICOM_TAG_SOURCE_IMAGE_SEQUENCE); + + // Individual tags + RemoveIfEnabled(dicom, DICOM_TAG_FRAME_OF_REFERENCE_UID); + + // The tags below should never occur at the first level of the + // hierarchy, but remove them anyway + RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID); + RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); + RemoveIfEnabled(dicom, DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID); + } + }; + + + bool DicomModification::CancelReplacement(const DicomTag& tag) + { + Replacements::iterator it = replacements_.find(tag); + + if (it != replacements_.end()) + { + delete it->second; + replacements_.erase(it); + return true; + } + else + { + return false; + } + } + + + void DicomModification::ReplaceInternal(const DicomTag& tag, + const Json::Value& value) + { + Replacements::iterator it = replacements_.find(tag); + + if (it != replacements_.end()) + { + delete it->second; + it->second = NULL; // In the case of an exception during the clone + it->second = new Json::Value(value); // Clone + } + else + { + replacements_[tag] = new Json::Value(value); // Clone + } + } + + + void DicomModification::ClearReplacements() + { + for (Replacements::iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + delete it->second; + } + + replacements_.clear(); + } + + + void DicomModification::MarkNotOrthancAnonymization() + { + Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); + + if (it != replacements_.end() && + (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 || + it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c)) + { + delete it->second; + replacements_.erase(it); + } + } + + + + std::string DicomModification::MapDicomIdentifier(const std::string& original, + ResourceType level) + { + std::string mapped; + + UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); + + if (previous == uidMap_.end()) + { + if (identifierGenerator_ == NULL) + { + mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); + } + else + { + if (!identifierGenerator_->Apply(mapped, original, level, currentSource_)) + { + LOG(ERROR) << "Unable to generate an anonymized ID"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); + } + else + { + mapped = previous->second; + } + + return mapped; + } + + + void DicomModification::MapDicomTags(ParsedDicomFile& dicom, + ResourceType level) + { + std::auto_ptr<DicomTag> tag; + + switch (level) + { + case ResourceType_Study: + tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + break; + + case ResourceType_Series: + tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + break; + + case ResourceType_Instance: + tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + std::string original; + if (!dicom.GetTagValue(original, *tag)) + { + original = ""; + } + + std::string mapped = MapDicomIdentifier(Toolbox::StripSpaces(original), level); + + dicom.Replace(*tag, mapped, + false /* don't try and decode data URI scheme for UIDs */, + DicomReplaceMode_InsertIfAbsent); + } + + + DicomModification::DicomModification() : + removePrivateTags_(false), + level_(ResourceType_Instance), + allowManualIdentifiers_(true), + keepStudyInstanceUid_(false), + keepSeriesInstanceUid_(false), + updateReferencedRelationships_(true), + isAnonymization_(false), + identifierGenerator_(NULL) + { + } + + DicomModification::~DicomModification() + { + ClearReplacements(); + } + + void DicomModification::Keep(const DicomTag& tag) + { + bool wasRemoved = IsRemoved(tag); + bool wasCleared = IsCleared(tag); + + removals_.erase(tag); + clearings_.erase(tag); + + bool wasReplaced = CancelReplacement(tag); + + if (tag == DICOM_TAG_STUDY_INSTANCE_UID) + { + keepStudyInstanceUid_ = true; + } + else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) + { + keepSeriesInstanceUid_ = true; + } + else if (tag.IsPrivate()) + { + privateTagsToKeep_.insert(tag); + } + else if (!wasRemoved && + !wasReplaced && + !wasCleared) + { + LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format(); + } + + MarkNotOrthancAnonymization(); + } + + void DicomModification::Remove(const DicomTag& tag) + { + removals_.insert(tag); + clearings_.erase(tag); + CancelReplacement(tag); + privateTagsToKeep_.erase(tag); + + MarkNotOrthancAnonymization(); + } + + void DicomModification::Clear(const DicomTag& tag) + { + removals_.erase(tag); + clearings_.insert(tag); + CancelReplacement(tag); + privateTagsToKeep_.erase(tag); + + MarkNotOrthancAnonymization(); + } + + bool DicomModification::IsRemoved(const DicomTag& tag) const + { + return removals_.find(tag) != removals_.end(); + } + + bool DicomModification::IsCleared(const DicomTag& tag) const + { + return clearings_.find(tag) != clearings_.end(); + } + + void DicomModification::Replace(const DicomTag& tag, + const Json::Value& value, + bool safeForAnonymization) + { + clearings_.erase(tag); + removals_.erase(tag); + privateTagsToKeep_.erase(tag); + ReplaceInternal(tag, value); + + if (!safeForAnonymization) + { + MarkNotOrthancAnonymization(); + } + } + + + bool DicomModification::IsReplaced(const DicomTag& tag) const + { + return replacements_.find(tag) != replacements_.end(); + } + + const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const + { + Replacements::const_iterator it = replacements_.find(tag); + + if (it == replacements_.end()) + { + throw OrthancException(ErrorCode_InexistentItem); + } + else + { + return *it->second; + } + } + + + std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const + { + const Json::Value& json = GetReplacement(tag); + + if (json.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return json.asString(); + } + } + + + void DicomModification::SetRemovePrivateTags(bool removed) + { + removePrivateTags_ = removed; + + if (!removed) + { + MarkNotOrthancAnonymization(); + } + } + + void DicomModification::SetLevel(ResourceType level) + { + uidMap_.clear(); + level_ = level; + + if (level != ResourceType_Patient) + { + MarkNotOrthancAnonymization(); + } + } + + + void DicomModification::SetupAnonymization2008() + { + // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles + // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf + + removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() + removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number + removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name + removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name + removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + //removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID => RelationshipsVisitor + removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) + //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) + removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids + removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number + removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() + //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() + removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID + //removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID => cf. RelationshipsVisitor + removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals_.insert(DicomTag(0x0040, 0xa124)); // UID + removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID + //removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID => RelationshipsVisitor + //removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID => RelationshipsVisitor + + // Some more removals (from the experience of DICOM files at the CHU of Liege) + removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address + removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers + removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + + // Set the DeidentificationMethod tag + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008); + } + + + void DicomModification::SetupAnonymization2017c() + { + /** + * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security + * and System Management Profiles), "basic profile" column. It was + * generated automatically with the + * "../Resources/GenerateAnonymizationProfile.py" script. + * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf + **/ + + // TODO: (50xx,xxxx) with rule X // Curve Data + // TODO: (60xx,3000) with rule X // Overlay Data + // TODO: (60xx,4000) with rule X // Overlay Comments + // Tag (0x0008, 0x0018) is set in Apply() /* U */ // SOP Instance UID + // Tag (0x0008, 0x1140) => RelationshipsVisitor /* X/Z/U* */ // Referenced Image Sequence + // Tag (0x0008, 0x1155) => RelationshipsVisitor /* U */ // Referenced SOP Instance UID + // Tag (0x0008, 0x2112) => RelationshipsVisitor /* X/Z/U* */ // Source Image Sequence + // Tag (0x0010, 0x0010) is set below (*) /* Z */ // Patient's Name + // Tag (0x0010, 0x0020) is set below (*) /* Z */ // Patient ID + // Tag (0x0020, 0x000d) is set in Apply() /* U */ // Study Instance UID + // Tag (0x0020, 0x000e) is set in Apply() /* U */ // Series Instance UID + // Tag (0x0020, 0x0052) => RelationshipsVisitor /* U */ // Frame of Reference UID + // Tag (0x3006, 0x0024) => RelationshipsVisitor /* U */ // Referenced Frame of Reference UID + // Tag (0x3006, 0x00c2) => RelationshipsVisitor /* U */ // Related Frame of Reference UID + clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date + clearings_.insert(DicomTag(0x0008, 0x0023)); /* Z/D */ // Content Date + clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time + clearings_.insert(DicomTag(0x0008, 0x0033)); /* Z/D */ // Content Time + clearings_.insert(DicomTag(0x0008, 0x0050)); // Accession Number + clearings_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + clearings_.insert(DicomTag(0x0008, 0x009c)); // Consulting Physician's Name + clearings_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + clearings_.insert(DicomTag(0x0018, 0x0010)); /* Z/D */ // Contrast Bolus Agent + clearings_.insert(DicomTag(0x0020, 0x0010)); // Study ID + clearings_.insert(DicomTag(0x0040, 0x1101)); /* D */ // Person Identification Code Sequence + clearings_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number / Imaging Service Request + clearings_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number / Imaging Service Request + clearings_.insert(DicomTag(0x0040, 0xa073)); /* D */ // Verifying Observer Sequence + clearings_.insert(DicomTag(0x0040, 0xa075)); /* D */ // Verifying Observer Name + clearings_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence + clearings_.insert(DicomTag(0x0040, 0xa123)); /* D */ // Person Name + clearings_.insert(DicomTag(0x0070, 0x0001)); /* D */ // Graphic Annotation Sequence + clearings_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name + removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID + removals_.insert(DicomTag(0x0000, 0x1001)); /* TODO UID */ // Requested SOP Instance UID + removals_.insert(DicomTag(0x0002, 0x0003)); /* TODO UID */ // Media Storage SOP Instance UID + removals_.insert(DicomTag(0x0004, 0x1511)); /* TODO UID */ // Referenced SOP Instance UID in File + removals_.insert(DicomTag(0x0008, 0x0014)); /* TODO UID */ // Instance Creator UID + removals_.insert(DicomTag(0x0008, 0x0015)); // Instance Coercion DateTime + removals_.insert(DicomTag(0x0008, 0x0021)); /* X/D */ // Series Date + removals_.insert(DicomTag(0x0008, 0x0022)); /* X/Z */ // Acquisition Date + removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date + removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date + removals_.insert(DicomTag(0x0008, 0x002a)); /* X/D */ // Acquisition DateTime + removals_.insert(DicomTag(0x0008, 0x0031)); /* X/D */ // Series Time + removals_.insert(DicomTag(0x0008, 0x0032)); /* X/Z */ // Acquisition Time + removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time + removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time + removals_.insert(DicomTag(0x0008, 0x0058)); /* TODO UID */ // Failed SOP Instance UID List + removals_.insert(DicomTag(0x0008, 0x0080)); /* X/Z/D */ // Institution Name + removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals_.insert(DicomTag(0x0008, 0x0082)); /* X/Z/D */ // Institution Code Sequence + removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician Identification Sequence + removals_.insert(DicomTag(0x0008, 0x009d)); // Consulting Physician Identification Sequence + removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + removals_.insert(DicomTag(0x0008, 0x1010)); /* X/Z/D */ // Station Name + removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals_.insert(DicomTag(0x0008, 0x1062)); // Physician(s) Reading Study Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1070)); /* X/Z/D */ // Operators' Name + removals_.insert(DicomTag(0x0008, 0x1072)); /* X/D */ // Operators' Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence + removals_.insert(DicomTag(0x0008, 0x1110)); /* X/Z */ // Referenced Study Sequence + removals_.insert(DicomTag(0x0008, 0x1111)); /* X/Z/D */ // Referenced Performed Procedure Step Sequence + removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence + removals_.insert(DicomTag(0x0008, 0x1195)); /* TODO UID */ // Transaction UID + removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + removals_.insert(DicomTag(0x0008, 0x3010)); /* TODO UID */ // Irradiation Event UID + removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments + removals_.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID + removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence + removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence + removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence + removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient IDs + removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence + removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name + removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals_.insert(DicomTag(0x0010, 0x1040)); // Patient Address + removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification + removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name + removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank + removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service + removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals_.insert(DicomTag(0x0010, 0x1100)); // Referenced Patient Photo Sequence + removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies + removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence + removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence + removals_.insert(DicomTag(0x0010, 0x2154)); // Patient's Telephone Numbers + removals_.insert(DicomTag(0x0010, 0x2155)); // Patient's Telecom Information + removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status + removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status + removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date + removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference + removals_.insert(DicomTag(0x0010, 0x2203)); /* X/Z */ // Patient Sex Neutered + removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person + removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization + removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals_.insert(DicomTag(0x0018, 0x1000)); /* X/Z/D */ // Device Serial Number + removals_.insert(DicomTag(0x0018, 0x1002)); /* TODO UID */ // Device UID + removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID + removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID + removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID + removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID + removals_.insert(DicomTag(0x0018, 0x1030)); /* X/D */ // Protocol Name + removals_.insert(DicomTag(0x0018, 0x1400)); /* X/D */ // Acquisition Device Processing Description + removals_.insert(DicomTag(0x0018, 0x2042)); /* TODO UID */ // Target UID + removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments + removals_.insert(DicomTag(0x0018, 0x700a)); /* X/D */ // Detector ID + removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description + removals_.insert(DicomTag(0x0018, 0x9516)); /* X/D */ // Start Acquisition DateTime + removals_.insert(DicomTag(0x0018, 0x9517)); /* X/D */ // End Acquisition DateTime + removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description + removals_.insert(DicomTag(0x0020, 0x0200)); /* TODO UID */ // Synchronization Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID + removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer + removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description + removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments + removals_.insert(DicomTag(0x0020, 0x9161)); /* TODO UID */ // Concatenation UID + removals_.insert(DicomTag(0x0020, 0x9164)); /* TODO UID */ // Dimension Organization UID + removals_.insert(DicomTag(0x0028, 0x1199)); /* TODO UID */ // Palette Color Lookup Table UID + removals_.insert(DicomTag(0x0028, 0x1214)); /* TODO UID */ // Large Palette Color Lookup Table UID + removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments + removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer + removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location + removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title + removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study + removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service + removals_.insert(DicomTag(0x0032, 0x1060)); /* X/Z */ // Requested Procedure Description + removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent + removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments + removals_.insert(DicomTag(0x0038, 0x0004)); // Referenced Patient Alias Sequence + removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID + removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID + removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence + removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date + removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time + removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description + removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs + removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description + removals_.insert(DicomTag(0x0038, 0x0300)); // Current Patient Location + removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence + removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State + removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments + removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title + removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date + removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time + removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name + removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence + removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name + removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location + removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication + removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title + removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name + removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location + removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0250)); // Performed Procedure Step End Date + removals_.insert(DicomTag(0x0040, 0x0251)); // Performed Procedure Step End Time + removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step + removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence + removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID + removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements + removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location + removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results + removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipients of Results Identification Sequence + removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address + removals_.insert(DicomTag(0x0040, 0x1103)); // Person's Telephone Numbers + removals_.insert(DicomTag(0x0040, 0x1104)); // Person's Telecom Information + removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments + removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for the Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By + removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location + removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number + removals_.insert(DicomTag(0x0040, 0x2011)); // Order Callback Telecom Information + removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments + removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description + removals_.insert(DicomTag(0x0040, 0x4005)); // Scheduled Procedure Step Start DateTime + removals_.insert(DicomTag(0x0040, 0x4010)); // Scheduled Procedure Step Modification DateTime + removals_.insert(DicomTag(0x0040, 0x4011)); // Expected Completion DateTime + removals_.insert(DicomTag(0x0040, 0x4023)); /* TODO UID */ // Referenced General Purpose Scheduled Procedure Step Transaction UID + removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4028)); // Performed Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization + removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name + removals_.insert(DicomTag(0x0040, 0x4050)); // Performed Procedure Step Start DateTime + removals_.insert(DicomTag(0x0040, 0x4051)); // Performed Procedure Step End DateTime + removals_.insert(DicomTag(0x0040, 0x4052)); // Procedure Step Cancellation DateTime + removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization + removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence + removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence + removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence + removals_.insert(DicomTag(0x0040, 0xa124)); /* TODO UID */ // UID + removals_.insert(DicomTag(0x0040, 0xa171)); /* TODO UID */ // Observation UID + removals_.insert(DicomTag(0x0040, 0xa172)); /* TODO UID */ // Referenced Observation UID (Trial) + removals_.insert(DicomTag(0x0040, 0xa192)); // Observation Date (Trial) + removals_.insert(DicomTag(0x0040, 0xa193)); // Observation Time (Trial) + removals_.insert(DicomTag(0x0040, 0xa307)); // Current Observer (Trial) + removals_.insert(DicomTag(0x0040, 0xa352)); // Verbal Source (Trial) + removals_.insert(DicomTag(0x0040, 0xa353)); // Address (Trial) + removals_.insert(DicomTag(0x0040, 0xa354)); // Telephone Number (Trial) + removals_.insert(DicomTag(0x0040, 0xa358)); // Verbal Source Identifier Code Sequence (Trial) + removals_.insert(DicomTag(0x0040, 0xa402)); /* TODO UID */ // Observation Subject UID (Trial) + removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals_.insert(DicomTag(0x0040, 0xdb0c)); /* TODO UID */ // Template Extension Organization UID + removals_.insert(DicomTag(0x0040, 0xdb0d)); /* TODO UID */ // Template Extension Creator UID + removals_.insert(DicomTag(0x0062, 0x0021)); /* TODO UID */ // Tracking UID + removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence + removals_.insert(DicomTag(0x0070, 0x031a)); /* TODO UID */ // Fiducial UID + removals_.insert(DicomTag(0x0070, 0x1101)); /* TODO UID */ // Presentation Display Collection UID + removals_.insert(DicomTag(0x0070, 0x1102)); /* TODO UID */ // Presentation Sequence Collection UID + removals_.insert(DicomTag(0x0088, 0x0140)); /* TODO UID */ // Storage Media File-set UID + removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence(see Note 12) + removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title + removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject + removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author + removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Keywords + removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID + removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence + removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence + removals_.insert(DicomTag(0x0400, 0x0404)); // MAC + removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence + removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + removals_.insert(DicomTag(0x2030, 0x0020)); // Text String + removals_.insert(DicomTag(0x3008, 0x0105)); // Source Serial Number + removals_.insert(DicomTag(0x300a, 0x0013)); /* TODO UID */ // Dose Reference UID + removals_.insert(DicomTag(0x300c, 0x0113)); // Reason for Omission Description + removals_.insert(DicomTag(0x300e, 0x0008)); /* X/Z */ // Reviewer Name + removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary + removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments + removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer + removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder + removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber + removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text + removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author + removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence + removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation + removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description + removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence + removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name + removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address + removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer + removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions + removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments + removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signatures Sequence + removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding + + // Set the DeidentificationMethod tag + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c); + } + + + void DicomModification::SetupAnonymization(DicomVersion version) + { + isAnonymization_ = true; + + removals_.clear(); + clearings_.clear(); + ClearReplacements(); + removePrivateTags_ = true; + level_ = ResourceType_Patient; + uidMap_.clear(); + privateTagsToKeep_.clear(); + + switch (version) + { + case DicomVersion_2008: + SetupAnonymization2008(); + break; + + case DicomVersion_2017c: + SetupAnonymization2017c(); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + // Set the PatientIdentityRemoved tag + ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); + + // (*) Choose a random patient name and ID + std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); + ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); + ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); + } + + void DicomModification::Apply(ParsedDicomFile& toModify) + { + // Check the request + assert(ResourceType_Patient + 1 == ResourceType_Study && + ResourceType_Study + 1 == ResourceType_Series && + ResourceType_Series + 1 == ResourceType_Instance); + + if (IsRemoved(DICOM_TAG_PATIENT_ID) || + IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || + IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || + IsRemoved(DICOM_TAG_SOP_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadRequest); + } + + + // Sanity checks at the patient level + if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (!allowManualIdentifiers_) + { + if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + } + + + // Sanity checks at the study level + if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying a study, the parent PatientID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (!allowManualIdentifiers_) + { + if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + } + + + // Sanity checks at the series level + if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying a series, the parent PatientID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a series, the parent StudyInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (!allowManualIdentifiers_) + { + if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + } + + + // Sanity checks at the instance level + if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying an instance, the parent PatientID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying an instance, the parent StudyInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + + // (0) Create a summary of the source file, if a custom generator + // is provided + if (identifierGenerator_ != NULL) + { + toModify.ExtractDicomSummary(currentSource_); + } + + + // (1) Remove the private tags, if need be + if (removePrivateTags_) + { + toModify.RemovePrivateTags(privateTagsToKeep_); + } + + // (2) Clear the tags specified by the user + for (SetOfTags::const_iterator it = clearings_.begin(); + it != clearings_.end(); ++it) + { + toModify.Clear(*it, true /* only clear if the tag exists in the original file */); + } + + // (3) Remove the tags specified by the user + for (SetOfTags::const_iterator it = removals_.begin(); + it != removals_.end(); ++it) + { + toModify.Remove(*it); + } + + // (4) Replace the tags + for (Replacements::const_iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent); + } + + // (5) Update the DICOM identifiers + if (level_ <= ResourceType_Study && + !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + if (keepStudyInstanceUid_) + { + LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!"; + } + else + { + MapDicomTags(toModify, ResourceType_Study); + } + } + + if (level_ <= ResourceType_Series && + !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + if (keepSeriesInstanceUid_) + { + LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!"; + } + else + { + MapDicomTags(toModify, ResourceType_Series); + } + } + + if (level_ <= ResourceType_Instance && // Always true + !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + MapDicomTags(toModify, ResourceType_Instance); + } + + // (6) Update the "referenced" relationships in the case of an anonymization + if (isAnonymization_) + { + RelationshipsVisitor visitor(*this); + + if (updateReferencedRelationships_) + { + toModify.Apply(visitor); + } + else + { + visitor.RemoveRelationships(toModify); + } + } + } + + + static bool IsDatabaseKey(const DicomTag& tag) + { + return (tag == DICOM_TAG_PATIENT_ID || + tag == DICOM_TAG_STUDY_INSTANCE_UID || + tag == DICOM_TAG_SERIES_INSTANCE_UID || + tag == DICOM_TAG_SOP_INSTANCE_UID); + } + + + static void ParseListOfTags(DicomModification& target, + const Json::Value& query, + DicomModification::TagOperation operation, + bool force) + { + if (!query.isArray()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) + { + if (query[i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + std::string name = query[i].asString(); + + DicomTag tag = FromDcmtkBridge::ParseTag(name); + + if (!force && IsDatabaseKey(tag)) + { + LOG(ERROR) << "Marking tag \"" << name << "\" as to be " + << (operation == DicomModification::TagOperation_Keep ? "kept" : "removed") + << " requires the \"Force\" option to be set to true"; + throw OrthancException(ErrorCode_BadRequest); + } + + switch (operation) + { + case DicomModification::TagOperation_Keep: + target.Keep(tag); + VLOG(1) << "Keep: " << name << " " << tag; + break; + + case DicomModification::TagOperation_Remove: + target.Remove(tag); + VLOG(1) << "Remove: " << name << " " << tag; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + static void ParseReplacements(DicomModification& target, + const Json::Value& replacements, + bool force) + { + if (!replacements.isObject()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + Json::Value::Members members = replacements.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + const Json::Value& value = replacements[name]; + + DicomTag tag = FromDcmtkBridge::ParseTag(name); + + if (!force && IsDatabaseKey(tag)) + { + LOG(ERROR) << "Marking tag \"" << name << "\" as to be replaced " + << "requires the \"Force\" option to be set to true"; + throw OrthancException(ErrorCode_BadRequest); + } + + target.Replace(tag, value, false); + + VLOG(1) << "Replace: " << name << " " << tag + << " == " << value.toStyledString(); + } + } + + + static bool GetBooleanValue(const std::string& member, + const Json::Value& json, + bool defaultValue) + { + if (!json.isMember(member)) + { + return defaultValue; + } + else if (json[member].type() == Json::booleanValue) + { + return json[member].asBool(); + } + else + { + LOG(ERROR) << "Member \"" << member << "\" should be a Boolean value"; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void DicomModification::ParseModifyRequest(const Json::Value& request) + { + if (!request.isObject()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + bool force = GetBooleanValue("Force", request, false); + + if (GetBooleanValue("RemovePrivateTags", request, false)) + { + SetRemovePrivateTags(true); + } + + if (request.isMember("Remove")) + { + ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); + } + + if (request.isMember("Replace")) + { + ParseReplacements(*this, request["Replace"], force); + } + + // The "Keep" operation only makes sense for the tags + // StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID. Avoid + // this feature as much as possible, as this breaks the DICOM + // model of the real world, except if you know exactly what + // you're doing! + if (request.isMember("Keep")) + { + ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); + } + } + + + void DicomModification::ParseAnonymizationRequest(bool& patientNameReplaced, + const Json::Value& request) + { + if (!request.isObject()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + bool force = GetBooleanValue("Force", request, false); + + // As of Orthanc 1.3.0, the default anonymization is done + // according to PS 3.15-2017c Table E.1-1 (basic profile) + DicomVersion version = DicomVersion_2017c; + if (request.isMember("DicomVersion")) + { + if (request["DicomVersion"].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + version = StringToDicomVersion(request["DicomVersion"].asString()); + } + } + + SetupAnonymization(version); + + std::string patientName = GetReplacementAsString(DICOM_TAG_PATIENT_NAME); + + if (GetBooleanValue("KeepPrivateTags", request, false)) + { + SetRemovePrivateTags(false); + } + + if (request.isMember("Remove")) + { + ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); + } + + if (request.isMember("Replace")) + { + ParseReplacements(*this, request["Replace"], force); + } + + if (request.isMember("Keep")) + { + ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); + } + + patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && + GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); + } + + + + + static const char* REMOVE_PRIVATE_TAGS = "RemovePrivateTags"; + static const char* LEVEL = "Level"; + static const char* ALLOW_MANUAL_IDENTIFIERS = "AllowManualIdentifiers"; + static const char* KEEP_STUDY_INSTANCE_UID = "KeepStudyInstanceUID"; + static const char* KEEP_SERIES_INSTANCE_UID = "KeepSeriesInstanceUID"; + static const char* UPDATE_REFERENCED_RELATIONSHIPS = "UpdateReferencedRelationships"; + static const char* IS_ANONYMIZATION = "IsAnonymization"; + static const char* REMOVALS = "Removals"; + static const char* CLEARINGS = "Clearings"; + static const char* PRIVATE_TAGS_TO_KEEP = "PrivateTagsToKeep"; + static const char* REPLACEMENTS = "Replacements"; + static const char* MAP_PATIENTS = "MapPatients"; + static const char* MAP_STUDIES = "MapStudies"; + static const char* MAP_SERIES = "MapSeries"; + static const char* MAP_INSTANCES = "MapInstances"; + + void DicomModification::Serialize(Json::Value& value) const + { + if (identifierGenerator_ != NULL) + { + LOG(ERROR) << "Cannot serialize a DicomModification with a custom identifier generator"; + throw OrthancException(ErrorCode_InternalError); + } + + value = Json::objectValue; + value[REMOVE_PRIVATE_TAGS] = removePrivateTags_; + value[LEVEL] = EnumerationToString(level_); + value[ALLOW_MANUAL_IDENTIFIERS] = allowManualIdentifiers_; + value[KEEP_STUDY_INSTANCE_UID] = keepStudyInstanceUid_; + value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; + value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; + value[IS_ANONYMIZATION] = isAnonymization_; + + SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); + SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); + SerializationToolbox::WriteSetOfTags(value, privateTagsToKeep_, PRIVATE_TAGS_TO_KEEP); + + Json::Value& tmp = value[REPLACEMENTS]; + + tmp = Json::objectValue; + + for (Replacements::const_iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + assert(it->second != NULL); + tmp[it->first.Format()] = *it->second; + } + + Json::Value& mapPatients = value[MAP_PATIENTS]; + Json::Value& mapStudies = value[MAP_STUDIES]; + Json::Value& mapSeries = value[MAP_SERIES]; + Json::Value& mapInstances = value[MAP_INSTANCES]; + + mapPatients = Json::objectValue; + mapStudies = Json::objectValue; + mapSeries = Json::objectValue; + mapInstances = Json::objectValue; + + for (UidMap::const_iterator it = uidMap_.begin(); it != uidMap_.end(); ++it) + { + Json::Value* tmp = NULL; + + switch (it->first.first) + { + case ResourceType_Patient: + tmp = &mapPatients; + break; + + case ResourceType_Study: + tmp = &mapStudies; + break; + + case ResourceType_Series: + tmp = &mapSeries; + break; + + case ResourceType_Instance: + tmp = &mapInstances; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + assert(tmp != NULL); + (*tmp) [it->first.second] = it->second; + } + } + + + void DicomModification::UnserializeUidMap(ResourceType level, + const Json::Value& serialized, + const char* field) + { + if (!serialized.isMember(field) || + serialized[field].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members names = serialized[field].getMemberNames(); + + for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) + { + const Json::Value& value = serialized[field][*it]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + uidMap_[std::make_pair(level, *it)] = value.asString(); + } + } + } + + + DicomModification::DicomModification(const Json::Value& serialized) : + identifierGenerator_(NULL) + { + removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS); + level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str()); + allowManualIdentifiers_ = SerializationToolbox::ReadBoolean(serialized, ALLOW_MANUAL_IDENTIFIERS); + keepStudyInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_STUDY_INSTANCE_UID); + keepSeriesInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SERIES_INSTANCE_UID); + updateReferencedRelationships_ = SerializationToolbox::ReadBoolean + (serialized, UPDATE_REFERENCED_RELATIONSHIPS); + isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); + + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); + SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); + SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP); + + if (!serialized.isMember(REPLACEMENTS) || + serialized[REPLACEMENTS].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members names = serialized[REPLACEMENTS].getMemberNames(); + + for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) + { + DicomTag tag(0, 0); + if (!DicomTag::ParseHexadecimal(tag, it->c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + const Json::Value& value = serialized[REPLACEMENTS][*it]; + replacements_.insert(std::make_pair(tag, new Json::Value(value))); + } + } + + UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS); + UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES); + UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES); + UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomModification.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,185 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ParsedDicomFile.h" + +namespace Orthanc +{ + class DicomModification : public boost::noncopyable + { + /** + * Process: + * (1) Remove private tags + * (2) Remove tags specified by the user + * (3) Replace tags + **/ + + public: + enum TagOperation + { + TagOperation_Keep, + TagOperation_Remove + }; + + class IDicomIdentifierGenerator : public boost::noncopyable + { + public: + virtual ~IDicomIdentifierGenerator() + { + } + + virtual bool Apply(std::string& target, + const std::string& sourceIdentifier, + ResourceType level, + const DicomMap& sourceDicom) = 0; + }; + + private: + class RelationshipsVisitor; + + typedef std::set<DicomTag> SetOfTags; + typedef std::map<DicomTag, Json::Value*> Replacements; + typedef std::map< std::pair<ResourceType, std::string>, std::string> UidMap; + + SetOfTags removals_; + SetOfTags clearings_; + Replacements replacements_; + bool removePrivateTags_; + ResourceType level_; + UidMap uidMap_; + SetOfTags privateTagsToKeep_; + bool allowManualIdentifiers_; + bool keepStudyInstanceUid_; + bool keepSeriesInstanceUid_; + bool updateReferencedRelationships_; + bool isAnonymization_; + DicomMap currentSource_; + + IDicomIdentifierGenerator* identifierGenerator_; + + std::string MapDicomIdentifier(const std::string& original, + ResourceType level); + + void MapDicomTags(ParsedDicomFile& dicom, + ResourceType level); + + void MarkNotOrthancAnonymization(); + + void ClearReplacements(); + + bool CancelReplacement(const DicomTag& tag); + + void ReplaceInternal(const DicomTag& tag, + const Json::Value& value); + + void SetupAnonymization2008(); + + void SetupAnonymization2017c(); + + void UnserializeUidMap(ResourceType level, + const Json::Value& serialized, + const char* field); + + public: + DicomModification(); + + DicomModification(const Json::Value& serialized); + + ~DicomModification(); + + void Keep(const DicomTag& tag); + + void Remove(const DicomTag& tag); + + // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) + void Clear(const DicomTag& tag); + + bool IsRemoved(const DicomTag& tag) const; + + bool IsCleared(const DicomTag& tag) const; + + // "safeForAnonymization" tells Orthanc that this replacement does + // not break the anonymization process it implements (for internal use only) + void Replace(const DicomTag& tag, + const Json::Value& value, // Encoded using UTF-8 + bool safeForAnonymization); + + bool IsReplaced(const DicomTag& tag) const; + + const Json::Value& GetReplacement(const DicomTag& tag) const; + + std::string GetReplacementAsString(const DicomTag& tag) const; + + void SetRemovePrivateTags(bool removed); + + bool ArePrivateTagsRemoved() const + { + return removePrivateTags_; + } + + void SetLevel(ResourceType level); + + ResourceType GetLevel() const + { + return level_; + } + + void SetupAnonymization(DicomVersion version); + + void Apply(ParsedDicomFile& toModify); + + void SetAllowManualIdentifiers(bool check) + { + allowManualIdentifiers_ = check; + } + + bool AreAllowManualIdentifiers() const + { + return allowManualIdentifiers_; + } + + void ParseModifyRequest(const Json::Value& request); + + void ParseAnonymizationRequest(bool& patientNameReplaced, + const Json::Value& request); + + void SetDicomIdentifierGenerator(IDicomIdentifierGenerator& generator) + { + identifierGenerator_ = &generator; + } + + void Serialize(Json::Value& value) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,2434 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" +#include "../Logging.h" +#include "../Toolbox.h" +#include "../OrthancException.h" + +#if ORTHANC_SANDBOXED == 0 +# include "../TemporaryFile.h" +#endif + +#include <list> +#include <limits> + +#include <boost/lexical_cast.hpp> +#include <boost/filesystem.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/join.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcistrmb.h> + +#include <dcmtk/dcmdata/dcvrae.h> +#include <dcmtk/dcmdata/dcvras.h> +#include <dcmtk/dcmdata/dcvrat.h> +#include <dcmtk/dcmdata/dcvrcs.h> +#include <dcmtk/dcmdata/dcvrda.h> +#include <dcmtk/dcmdata/dcvrds.h> +#include <dcmtk/dcmdata/dcvrdt.h> +#include <dcmtk/dcmdata/dcvrfd.h> +#include <dcmtk/dcmdata/dcvrfl.h> +#include <dcmtk/dcmdata/dcvris.h> +#include <dcmtk/dcmdata/dcvrlo.h> +#include <dcmtk/dcmdata/dcvrlt.h> +#include <dcmtk/dcmdata/dcvrpn.h> +#include <dcmtk/dcmdata/dcvrsh.h> +#include <dcmtk/dcmdata/dcvrsl.h> +#include <dcmtk/dcmdata/dcvrss.h> +#include <dcmtk/dcmdata/dcvrst.h> +#include <dcmtk/dcmdata/dcvrtm.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcvrul.h> +#include <dcmtk/dcmdata/dcvrus.h> +#include <dcmtk/dcmdata/dcvrut.h> + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 +# include <EmbeddedResources.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 +# include <dcmtk/dcmjpeg/djdecode.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 +# include <dcmtk/dcmjpls/djdecode.h> +#endif + + +namespace Orthanc +{ +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, + EmbeddedResources::FileResourceId resource) + { + std::string content; + EmbeddedResources::GetFileResource(content, resource); + +#if ORTHANC_SANDBOXED == 0 + TemporaryFile tmp; + tmp.Write(content); + + if (!dictionary.loadDictionary(tmp.GetPath().c_str())) + { + LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " + << "your TEMP directory does not contain special characters."; + throw OrthancException(ErrorCode_InternalError); + } +#else + if (!dictionary.loadFromMemory(content)) + { + LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " + << "your TEMP directory does not contain special characters."; + throw OrthancException(ErrorCode_InternalError); + } +#endif + } + +#else + static void LoadExternalDictionary(DcmDataDictionary& dictionary, + const std::string& directory, + const std::string& filename) + { + boost::filesystem::path p = directory; + p = p / filename; + + LOG(WARNING) << "Loading the external DICOM dictionary " << p; + + if (!dictionary.loadDictionary(p.string().c_str())) + { + throw OrthancException(ErrorCode_InternalError); + } + } +#endif + + + namespace + { + class DictionaryLocker + { + private: + DcmDataDictionary& dictionary_; + + public: + DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) + { + } + + ~DictionaryLocker() + { + dcmDataDict.unlock(); + } + + DcmDataDictionary& operator*() + { + return dictionary_; + } + + DcmDataDictionary* operator->() + { + return &dictionary_; + } + }; + + +#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) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64) + + + template <typename F> + static DicomValue* ApplyDcmtkToCTypeConverter(DcmElement& element) + { + F f; + typename F::CType value; + + if (element.getLength() > sizeof(typename F::CType) + && (element.getLength() % sizeof(typename F::CType)) == 0) + { + size_t count = element.getLength() / sizeof(typename F::CType); + std::vector<std::string> strings; + for (size_t i = 0; i < count; i++) { + if (f.Apply(value, element, i)) { + strings.push_back(boost::lexical_cast<std::string>(value)); + } + } + return new DicomValue(boost::algorithm::join(strings, "\\"), false); + } + else if (f.Apply(value, element, 0)) { + return new DicomValue(boost::lexical_cast<std::string>(value), false); + } + else { + return new DicomValue; + } + } + + } + + + void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) + { + LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER; + + { + DictionaryLocker locker; + + locker->clear(); + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + LOG(INFO) << "Loading the embedded dictionaries"; + /** + * Do not load DICONDE dictionary, it breaks the other tags. The + * command "strace storescu 2>&1 |grep dic" shows that DICONDE + * dictionary is not loaded by storescu. + **/ + //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE); + + LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM); + + if (loadPrivateDictionary) + { + LOG(INFO) << "Loading the embedded dictionary of private tags"; + LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE); + } + else + { + LOG(INFO) << "The dictionary of private tags has not been loaded"; + } + +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) + std::string path = DCMTK_DICTIONARY_DIR; + + const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); + if (env != NULL) + { + path = std::string(env); + } + + LoadExternalDictionary(*locker, path, "dicom.dic"); + + if (loadPrivateDictionary) + { + LoadExternalDictionary(*locker, path, "private.dic"); + } + else + { + LOG(INFO) << "The dictionary of private tags has not been loaded"; + } + +#else +#error Support your platform here +#endif + } + + /* make sure data dictionary is loaded */ + if (!dcmDataDict.isDictionaryLoaded()) + { + LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; + throw OrthancException(ErrorCode_InternalError); + } + + { + // Test the dictionary with a simple DICOM tag + DcmTag key(0x0010, 0x1030); // This is PatientWeight + if (key.getEVR() != EVR_DS) + { + LOG(ERROR) << "The DICOM dictionary has not been correctly read"; + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, + ValueRepresentation vr, + const std::string& name, + unsigned int minMultiplicity, + unsigned int maxMultiplicity, + const std::string& privateCreator) + { + if (minMultiplicity < 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + bool arbitrary = false; + if (maxMultiplicity == 0) + { + maxMultiplicity = DcmVariableVM; + arbitrary = true; + } + else if (maxMultiplicity < minMultiplicity) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + DcmEVR evr = ToDcmtkBridge::Convert(vr); + + LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " + << name << " (multiplicity: " << minMultiplicity << "-" + << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")"; + + std::auto_ptr<DcmDictEntry> entry; + if (privateCreator.empty()) + { + if (tag.GetGroup() % 2 == 1) + { + char buf[128]; + sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), " + "but no private creator was associated with it", + tag.GetGroup(), tag.GetElement()); + LOG(WARNING) << buf; + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast<int>(minMultiplicity), + static_cast<int>(maxMultiplicity), + NULL /* version */, + OFTrue /* doCopyString */, + NULL /* private creator */)); + } + else + { + // "Private Data Elements have an odd Group Number that is not + // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or + // (FFFF,eeee)." + if (tag.GetGroup() % 2 == 0 /* even */ || + tag.GetGroup() == 0x0001 || + tag.GetGroup() == 0x0003 || + tag.GetGroup() == 0x0005 || + tag.GetGroup() == 0x0007 || + tag.GetGroup() == 0xffff) + { + char buf[128]; + sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009", + tag.GetGroup(), tag.GetElement()); + LOG(ERROR) << buf; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast<int>(minMultiplicity), + static_cast<int>(maxMultiplicity), + "private" /* version */, + OFTrue /* doCopyString */, + privateCreator.c_str())); + } + + entry->setGroupRangeRestriction(DcmDictRange_Unspecified); + entry->setElementRangeRestriction(DcmDictRange_Unspecified); + + { + DictionaryLocker locker; + + if (locker->findEntry(name.c_str())) + { + LOG(ERROR) << "Cannot register two tags with the same symbolic name \"" << name << "\""; + throw OrthancException(ErrorCode_AlreadyExistingTag); + } + + locker->addEntry(entry.release()); + } + } + + + Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset, + Encoding defaultEncoding) + { + Encoding encoding = defaultEncoding; + + OFString tmp; + if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) + { + std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); + + if (characterSet.empty()) + { + // Empty specific character set tag: Use the default encoding + } + else if (GetDicomEncoding(encoding, characterSet.c_str())) + { + // The specific character set is supported by the Orthanc core + } + else + { + LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet + << ", fallback to ASCII (remove all special characters)"; + encoding = Encoding_Ascii; + } + } + else + { + // No specific character set tag: Use the default encoding + } + + return encoding; + } + + + void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, + DcmItem& dataset, + unsigned int maxStringLength, + Encoding defaultEncoding) + { + std::set<DicomTag> ignoreTagLength; + + Encoding encoding = DetectEncoding(dataset, defaultEncoding); + + target.Clear(); + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element && element->isLeaf()) + { + target.SetValue(element->getTag().getGTag(), + element->getTag().getETag(), + ConvertLeafElement(*element, DicomToJsonFlags_Default, + maxStringLength, encoding, ignoreTagLength)); + } + } + } + + + DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) + { + return DicomTag(tag.getGTag(), tag.getETag()); + } + + + DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) + { + return DicomTag(element.getGTag(), element.getETag()); + } + + + DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + const std::set<DicomTag>& ignoreTagLength) + { + if (!element.isLeaf()) + { + // This function is only applicable to leaf elements + throw OrthancException(ErrorCode_BadParameterType); + } + + char *c = NULL; + if (element.isaString() && + element.getString(c).good()) + { + if (c == NULL) // This case corresponds to the empty string + { + return new DicomValue("", false); + } + else + { + std::string s(c); + std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); + + if (maxStringLength != 0 && + utf8.size() > maxStringLength && + ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + { + return new DicomValue; // Too long, create a NULL value + } + else + { + return new DicomValue(utf8, false); + } + } + } + + + if (element.getVR() == EVR_UN) + { + // Unknown value representation: Lookup in the dictionary. This + // is notably the case for private tags registered with the + // "Dictionary" configuration option. + DictionaryLocker locker; + + const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), + element.getTag().getPrivateCreator()); + if (entry != NULL && + entry->getVR().isaString()) + { + Uint8* data = NULL; + + // At (*), we do not try and convert to UTF-8, as nothing says + // the encoding of the private tag is the same as that of the + // remaining of the DICOM dataset. Only go for ASCII strings. + + if (element.getUint8Array(data) == EC_Normal && + Toolbox::IsAsciiString(data, element.getLength())) // (*) + { + if (data == NULL) + { + return new DicomValue("", false); // Empty string + } + else if (maxStringLength != 0 && + element.getLength() > maxStringLength && + ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + { + return new DicomValue; // Too long, create a NULL value + } + else + { + std::string s(reinterpret_cast<const char*>(data), element.getLength()); + return new DicomValue(s, false); + } + } + } + } + + + try + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + switch (element.getVR()) + { + + /** + * Deal with binary data (including PixelData). + **/ + + 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 + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + { + if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) + { + Uint8* data = NULL; + if (element.getUint8Array(data) == EC_Normal) + { + return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true); + } + } + + return new DicomValue; + } + + /** + * Numeric types + **/ + + case EVR_SL: // signed long + { + return ApplyDcmtkToCTypeConverter<DcmtkToSint32Converter>(element); + } + + case EVR_SS: // signed short + { + return ApplyDcmtkToCTypeConverter<DcmtkToSint16Converter>(element); + } + + case EVR_UL: // unsigned long + { + return ApplyDcmtkToCTypeConverter<DcmtkToUint32Converter>(element); + } + + case EVR_US: // unsigned short + { + return ApplyDcmtkToCTypeConverter<DcmtkToUint16Converter>(element); + } + + case EVR_FL: // float single-precision + { + return ApplyDcmtkToCTypeConverter<DcmtkToFloat32Converter>(element); + } + + case EVR_FD: // float double-precision + { + return ApplyDcmtkToCTypeConverter<DcmtkToFloat64Converter>(element); + } + + + /** + * Attribute tag. + **/ + + case EVR_AT: + { + DcmTagKey tag; + if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good()) + { + DicomTag t(tag.getGroup(), tag.getElement()); + return new DicomValue(t.Format(), false); + } + else + { + return new DicomValue; + } + } + + + /** + * Sequence types, should never occur at this point because of + * "element.isLeaf()". + **/ + + case EVR_SQ: // sequence of items + return new DicomValue; + + + /** + * 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_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + return new DicomValue; + + + /** + * Default case. + **/ + + default: + return new DicomValue; + } + } + catch (boost::bad_lexical_cast&) + { + return new DicomValue; + } + catch (std::bad_cast&) + { + return new DicomValue; + } + } + + + static Json::Value& PrepareNode(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format) + { + assert(parent.type() == Json::objectValue); + + DicomTag tag(FromDcmtkBridge::GetTag(element)); + const std::string formattedTag = tag.Format(); + + if (format == DicomToJsonFormat_Short) + { + parent[formattedTag] = Json::nullValue; + return parent[formattedTag]; + } + + // This code gives access to the name of the private tags + std::string tagName = FromDcmtkBridge::GetTagName(element); + + switch (format) + { + case DicomToJsonFormat_Human: + parent[tagName] = Json::nullValue; + return parent[tagName]; + + case DicomToJsonFormat_Full: + { + parent[formattedTag] = Json::objectValue; + Json::Value& node = parent[formattedTag]; + + if (element.isLeaf()) + { + node["Name"] = tagName; + + if (element.getTag().getPrivateCreator() != NULL) + { + node["PrivateCreator"] = element.getTag().getPrivateCreator(); + } + + return node; + } + else + { + node["Name"] = tagName; + node["Type"] = "Sequence"; + node["Value"] = Json::nullValue; + return node["Value"]; + } + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static void LeafValueToJson(Json::Value& target, + const DicomValue& value, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + Json::Value* targetValue = NULL; + Json::Value* targetType = NULL; + + switch (format) + { + case DicomToJsonFormat_Short: + case DicomToJsonFormat_Human: + { + assert(target.type() == Json::nullValue); + targetValue = ⌖ + break; + } + + case DicomToJsonFormat_Full: + { + assert(target.type() == Json::objectValue); + target["Value"] = Json::nullValue; + target["Type"] = Json::nullValue; + targetType = &target["Type"]; + targetValue = &target["Value"]; + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + assert(targetValue != NULL); + assert(targetValue->type() == Json::nullValue); + assert(targetType == NULL || targetType->type() == Json::nullValue); + + if (value.IsNull()) + { + if (targetType != NULL) + { + *targetType = "Null"; + } + } + else if (value.IsBinary()) + { + if (flags & DicomToJsonFlags_ConvertBinaryToAscii) + { + *targetValue = Toolbox::ConvertToAscii(value.GetContent()); + } + else + { + std::string s; + value.FormatDataUriScheme(s); + *targetValue = s; + } + + if (targetType != NULL) + { + *targetType = "Binary"; + } + } + else if (maxStringLength == 0 || + value.GetContent().size() <= maxStringLength) + { + *targetValue = value.GetContent(); + + if (targetType != NULL) + { + *targetType = "String"; + } + } + else + { + if (targetType != NULL) + { + *targetType = "TooLong"; + } + } + } + + + void FromDcmtkBridge::ElementToJson(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + const std::set<DicomTag>& ignoreTagLength) + { + if (parent.type() == Json::nullValue) + { + parent = Json::objectValue; + } + + assert(parent.type() == Json::objectValue); + Json::Value& target = PrepareNode(parent, element, format); + + if (element.isLeaf()) + { + // The "0" below lets "LeafValueToJson()" take care of "TooLong" values + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + (element, flags, 0, encoding, ignoreTagLength)); + + if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + { + LeafValueToJson(target, *v, format, flags, maxStringLength); + } + else + { + LeafValueToJson(target, *v, format, flags, 0); + } + } + else + { + assert(target.type() == Json::nullValue); + target = Json::arrayValue; + + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + DcmItem* child = sequence.getItem(i); + Json::Value& v = target.append(Json::objectValue); + DatasetToJson(v, *child, format, flags, maxStringLength, encoding, ignoreTagLength); + } + } + } + + + void FromDcmtkBridge::DatasetToJson(Json::Value& parent, + DcmItem& item, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + const std::set<DicomTag>& ignoreTagLength) + { + assert(parent.type() == Json::objectValue); + + for (unsigned long i = 0; i < item.card(); i++) + { + DcmElement* element = item.getElement(i); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); + + /*element->getTag().isPrivate()*/ + if (tag.IsPrivate() && + !(flags & DicomToJsonFlags_IncludePrivateTags)) + { + continue; + } + + if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) + { + DictionaryLocker locker; + if (locker->findEntry(element->getTag(), NULL) == NULL) + { + continue; + } + } + + DcmEVR evr = element->getTag().getEVR(); + if (evr == EVR_OB || + evr == EVR_OF || + evr == EVR_OW || + evr == EVR_UN || + evr == EVR_ox) + { + // This is a binary tag + if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || + (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) + { + continue; + } + } + + FromDcmtkBridge::ElementToJson(parent, *element, format, flags, + maxStringLength, encoding, ignoreTagLength); + } + } + + + void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding defaultEncoding, + const std::set<DicomTag>& ignoreTagLength) + { + Encoding encoding = DetectEncoding(dataset, defaultEncoding); + + target = Json::objectValue; + DatasetToJson(target, dataset, format, flags, maxStringLength, encoding, ignoreTagLength); + } + + + void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, + DcmMetaInfo& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + std::set<DicomTag> ignoreTagLength; + target = Json::objectValue; + DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, ignoreTagLength); + } + + + + static std::string GetTagNameInternal(DcmTag& tag) + { + { + // Some patches for important tags because of different DICOM + // dictionaries between DCMTK versions + DicomTag tmp(tag.getGroup(), tag.getElement()); + std::string n = tmp.GetMainTagsName(); + if (n.size() != 0) + { + return n; + } + // End of patches + } + +#if 0 + // This version explicitly calls the dictionary + const DcmDataDictionary& dict = dcmDataDict.rdlock(); + const DcmDictEntry* entry = dict.findEntry(tag, NULL); + + std::string s(DcmTag_ERROR_TagName); + if (entry != NULL) + { + s = std::string(entry->getTagName()); + } + + dcmDataDict.unlock(); + return s; +#else + const char* name = tag.getTagName(); + if (name == NULL) + { + return DcmTag_ERROR_TagName; + } + else + { + return std::string(name); + } +#endif + } + + + std::string FromDcmtkBridge::GetTagName(const DicomTag& t, + const std::string& privateCreator) + { + DcmTag tag(t.GetGroup(), t.GetElement()); + + if (!privateCreator.empty()) + { + tag.setPrivateCreator(privateCreator.c_str()); + } + + return GetTagNameInternal(tag); + } + + + std::string FromDcmtkBridge::GetTagName(const DcmElement& element) + { + // Copy the tag to ensure const-correctness of DcmElement. Note + // that the private creator information is also copied. + DcmTag tag(element.getTag()); + + return GetTagNameInternal(tag); + } + + + + DicomTag FromDcmtkBridge::ParseTag(const char* name) + { + DicomTag parsed(0, 0); + if (DicomTag::ParseHexadecimal(parsed, name)) + { + return parsed; + } + +#if 0 + const DcmDataDictionary& dict = dcmDataDict.rdlock(); + const DcmDictEntry* entry = dict.findEntry(name); + + if (entry == NULL) + { + dcmDataDict.unlock(); + throw OrthancException(ErrorCode_UnknownDicomTag); + } + else + { + DcmTagKey key = entry->getKey(); + DicomTag tag(key.getGroup(), key.getElement()); + dcmDataDict.unlock(); + return tag; + } +#else + DcmTag tag; + if (DcmTag::findTagFromName(name, tag).good()) + { + return DicomTag(tag.getGTag(), tag.getETag()); + } + else + { + LOG(INFO) << "Unknown DICOM tag: \"" << name << "\""; + throw OrthancException(ErrorCode_UnknownDicomTag); + } +#endif + } + + + bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) + { + DcmTag tmp(tag.GetGroup(), tag.GetElement()); + return tmp.isUnknownVR(); + } + + + void FromDcmtkBridge::ToJson(Json::Value& result, + const DicomMap& values, + bool simplify) + { + if (result.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + result.clear(); + + for (DicomMap::Map::const_iterator + it = values.map_.begin(); it != values.map_.end(); ++it) + { + // TODO Inject PrivateCreator if some is available in the DicomMap? + const std::string tagName = GetTagName(it->first, ""); + + if (simplify) + { + if (it->second->IsNull()) + { + result[tagName] = Json::nullValue; + } + else + { + // TODO IsBinary + result[tagName] = it->second->GetContent(); + } + } + else + { + Json::Value value = Json::objectValue; + + value["Name"] = tagName; + + if (it->second->IsNull()) + { + value["Type"] = "Null"; + value["Value"] = Json::nullValue; + } + else + { + // TODO IsBinary + value["Type"] = "String"; + value["Value"] = it->second->GetContent(); + } + + result[it->first.Format()] = value; + } + } + } + + + std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) + { + char uid[100]; + + switch (level) + { + case ResourceType_Patient: + // The "PatientID" field is of type LO (Long String), 64 + // Bytes Maximum. An UUID is of length 36, thus it can be used + // as a random PatientID. + return Toolbox::GenerateUuid(); + + case ResourceType_Instance: + return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); + + case ResourceType_Series: + return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); + + case ResourceType_Study: + return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmDataset& dataSet) + { + // 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; + } + } + + + ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) + { + DcmTag t(tag.GetGroup(), tag.GetElement()); + return Convert(t.getEVR()); + } + + ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr) + { + switch (vr) + { + case EVR_AE: + return ValueRepresentation_ApplicationEntity; + + case EVR_AS: + return ValueRepresentation_AgeString; + + case EVR_AT: + return ValueRepresentation_AttributeTag; + + case EVR_CS: + return ValueRepresentation_CodeString; + + case EVR_DA: + return ValueRepresentation_Date; + + case EVR_DS: + return ValueRepresentation_DecimalString; + + case EVR_DT: + return ValueRepresentation_DateTime; + + case EVR_FL: + return ValueRepresentation_FloatingPointSingle; + + case EVR_FD: + return ValueRepresentation_FloatingPointDouble; + + case EVR_IS: + return ValueRepresentation_IntegerString; + + case EVR_LO: + return ValueRepresentation_LongString; + + case EVR_LT: + return ValueRepresentation_LongText; + + case EVR_OB: + return ValueRepresentation_OtherByte; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_OD: + return ValueRepresentation_OtherDouble;*/ + + case EVR_OF: + return ValueRepresentation_OtherFloat; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_OL: + return ValueRepresentation_OtherLong;*/ + + case EVR_OW: + return ValueRepresentation_OtherWord; + + case EVR_PN: + return ValueRepresentation_PersonName; + + case EVR_SH: + return ValueRepresentation_ShortString; + + case EVR_SL: + return ValueRepresentation_SignedLong; + + case EVR_SQ: + return ValueRepresentation_Sequence; + + case EVR_SS: + return ValueRepresentation_SignedShort; + + case EVR_ST: + return ValueRepresentation_ShortText; + + case EVR_TM: + return ValueRepresentation_Time; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_UC: + return ValueRepresentation_UnlimitedCharacters;*/ + + case EVR_UI: + return ValueRepresentation_UniqueIdentifier; + + case EVR_UL: + return ValueRepresentation_UnsignedLong; + + case EVR_UN: + return ValueRepresentation_Unknown; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_UR: + return ValueRepresentation_UniversalResource;*/ + + case EVR_US: + return ValueRepresentation_UnsignedShort; + + case EVR_UT: + return ValueRepresentation_UnlimitedText; + + default: + return ValueRepresentation_NotSupported; + } + } + + + static bool IsBinaryTag(const DcmTag& key) + { + return (key.isUnknownVR() || + 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)) + { + return new DcmOtherByteOtherWord(key); + } + + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * Binary types, handled above + **/ + + 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); + + + /** + * 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); + } + + + + void FromDcmtkBridge::FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme, + Encoding dicomEncoding) + { + std::string binary; + const std::string* decoded = &utf8Value; + + if (decodeDataUriScheme && + boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) + { + std::string mime; + if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + decoded = &binary; + } + else if (dicomEncoding != Encoding_Utf8) + { + binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); + decoded = &binary; + } + + DcmTag key(tag.GetGroup(), tag.GetElement()); + + if (tag.IsPrivate() || + IsBinaryTag(key)) + { + if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good()) + { + return; + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + bool ok = false; + + try + { + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + case EVR_UN: // unknown value representation + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * String types. + **/ + + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + { + ok = element.putString(decoded->c_str()).good(); + break; + } + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + { + ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good(); + break; + } + + case EVR_SS: // signed short + { + ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good(); + break; + } + + case EVR_UL: // unsigned long + { + ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good(); + break; + } + + case EVR_US: // unsigned short + { + ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good(); + break; + } + + case EVR_FL: // float single-precision + { + ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good(); + break; + } + + case EVR_FD: // float double-precision + { + ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good(); + break; + } + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + { + ok = false; + break; + } + + + /** + * Internal to DCMTK. + **/ + + case EVR_ox: // OB or OW depending on context + 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; + } + } + catch (boost::bad_lexical_cast&) + { + ok = false; + } + + if (!ok) + { + LOG(ERROR) << "While creating a DICOM instance, tag (" << tag.Format() + << ") has out-of-range value: \"" << *decoded << "\""; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme, + Encoding dicomEncoding) + { + std::auto_ptr<DcmElement> element; + + switch (value.type()) + { + case Json::stringValue: + element.reset(CreateElementForTag(tag)); + FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding); + break; + + case Json::nullValue: + element.reset(CreateElementForTag(tag)); + FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding); + break; + + case Json::arrayValue: + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + if (key.getEVR() != EVR_SQ) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key); + element.reset(sequence); + + for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) + { + std::auto_ptr<DcmItem> item(new DcmItem); + + switch (value[i].type()) + { + case Json::objectValue: + { + 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)); + } + break; + } + + case Json::arrayValue: + { + // Lua cannot disambiguate between an empty dictionary + // and an empty array + if (value[i].size() != 0) + { + throw OrthancException(ErrorCode_BadParameterType); + } + break; + } + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + + sequence->append(item.release()); + } + + break; + } + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + + return element.release(); + } + + + DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset) + { + DcmElement *element = NULL; + if (!dataset.findAndGetElement(DCM_PixelData, element).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + DcmPixelSequence* pixelSequence = NULL; + if (!pixelData.getEncapsulatedRepresentation + (dataset.getOriginalXfer(), NULL, pixelSequence).good()) + { + return NULL; + } + else + { + return pixelSequence; + } + } + + + Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json, + Encoding defaultEncoding) + { + if (json.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + Encoding encoding = defaultEncoding; + + const Json::Value::Members tags = json.getMemberNames(); + + // Look for SpecificCharacterSet (0008,0005) in the JSON file + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + const Json::Value& value = json[tags[i]]; + if (value.type() != Json::stringValue || + (value.asString().length() != 0 && + !GetDicomEncoding(encoding, value.asCString()))) + { + LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value; + throw OrthancException(ErrorCode_BadRequest); + } + + if (value.asString().length() == 0) + { + return defaultEncoding; + } + } + } + + return encoding; + } + + + static void SetString(DcmDataset& target, + const DcmTag& tag, + const std::string& value) + { + if (!target.putAndInsertString(tag, value.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 + bool generateIdentifiers, + bool decodeDataUriScheme, + Encoding defaultEncoding) + { + std::auto_ptr<DcmDataset> result(new DcmDataset); + Encoding encoding = ExtractEncoding(json, defaultEncoding); + + SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding)); + + const Json::Value::Members tags = json.getMemberNames(); + + bool hasPatientId = false; + bool hasStudyInstanceUid = false; + bool hasSeriesInstanceUid = false; + bool hasSopInstanceUid = false; + + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + const Json::Value& value = json[tags[i]]; + + if (tag == DICOM_TAG_PATIENT_ID) + { + hasPatientId = true; + } + else if (tag == DICOM_TAG_STUDY_INSTANCE_UID) + { + hasStudyInstanceUid = true; + } + else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) + { + hasSeriesInstanceUid = true; + } + else if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + hasSopInstanceUid = true; + } + + if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + const DcmTagKey& tag = element->getTag(); + + result->findAndDeleteElement(tag); + + DcmElement* tmp = element.release(); + if (!result->insert(tmp, false, false).good()) + { + delete tmp; + throw OrthancException(ErrorCode_InternalError); + } + } + } + + if (!hasPatientId && + generateIdentifiers) + { + SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient)); + } + + if (!hasStudyInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study)); + } + + if (!hasSeriesInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series)); + } + + if (!hasSopInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance)); + } + + return result.release(); + } + + + DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer, + size_t size) + { + DcmInputBufferStream is; + if (size > 0) + { + is.setBuffer(buffer, size); + } + is.setEos(); + + std::auto_ptr<DcmFileFormat> result(new DcmFileFormat); + + result->transferInit(); + if (!result->read(is).good()) + { + LOG(ERROR) << "Cannot parse an invalid DICOM file (size: " << size << " bytes)"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + result->loadAllDataIntoMemory(); + result->transferEnd(); + + return result.release(); + } + + + void FromDcmtkBridge::FromJson(DicomMap& target, + const Json::Value& source) + { + if (source.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + target.Clear(); + + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = source[members[i]]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + target.SetValue(ParseTag(members[i]), value.asString(), false); + } + } + + + void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset, + Encoding source, + Encoding target) + { + // Recursive exploration of a dataset to change the encoding of + // each string-like element + + if (source == target) + { + return; + } + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element) + { + if (element->isLeaf()) + { + char *c = NULL; + if (element->isaString() && + element->getString(c).good() && + c != NULL) + { + std::string a = Toolbox::ConvertToUtf8(c, source); + std::string b = Toolbox::ConvertFromUtf8(a, target); + element->putString(b.c_str()); + } + } + else + { + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + + for (unsigned long j = 0; j < sequence.card(); j++) + { + ChangeStringEncoding(*sequence.getItem(j), source, target); + } + } + } + } + } + + + bool FromDcmtkBridge::LookupTransferSyntax(std::string& result, + DcmFileFormat& dicom) + { + const char* value = NULL; + + if (dicom.getMetaInfo() != NULL && + dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() && + value != NULL) + { + result.assign(value); + return true; + } + else + { + return false; + } + } + + +#if ORTHANC_ENABLE_LUA == 1 + void FromDcmtkBridge::ExecuteToDicom(DicomMap& target, + LuaFunctionCall& call) + { + Json::Value output; + call.ExecuteToJson(output, true /* keep strings */); + + target.Clear(); + + if (output.type() == Json::arrayValue && + output.size() == 0) + { + // This case happens for empty tables + return; + } + + if (output.type() != Json::objectValue) + { + LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table"; + throw OrthancException(ErrorCode_LuaBadOutput); + } + + Json::Value::Members members = output.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + if (output[members[i]].type() != Json::stringValue) + { + LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table mapping names of DICOM tags to strings"; + throw OrthancException(ErrorCode_LuaBadOutput); + } + + DicomTag tag(ParseTag(members[i])); + target.SetValue(tag, output[members[i]].asString(), false); + } + } +#endif + + + void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, + DcmItem& dataset) + { + ExtractDicomSummary(target, dataset, + ORTHANC_MAXIMUM_TAG_LENGTH, + GetDefaultDicomEncoding()); + } + + + void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + const std::set<DicomTag>& ignoreTagLength) + { + ExtractDicomAsJson(target, dataset, + DicomToJsonFormat_Full, + DicomToJsonFlags_Default, + ORTHANC_MAXIMUM_TAG_LENGTH, + GetDefaultDicomEncoding(), + ignoreTagLength); + } + + + void FromDcmtkBridge::InitializeCodecs() + { +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + LOG(INFO) << "Registering JPEG Lossless codecs in DCMTK"; + DJLSDecoderRegistration::registerCodecs(); +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + LOG(INFO) << "Registering JPEG codecs in DCMTK"; + DJDecoderRegistration::registerCodecs(); +#endif + } + + + void FromDcmtkBridge::FinalizeCodecs() + { +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + // Unregister JPEG-LS codecs + DJLSDecoderRegistration::cleanup(); +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + // Unregister JPEG codecs + DJDecoderRegistration::cleanup(); +#endif + } + + + + // Forward declaration + static void ApplyVisitorToElement(DcmElement& element, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + Encoding encoding); + + static void ApplyVisitorToDataset(DcmItem& dataset, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + Encoding encoding) + { + assert(parentTags.size() == parentIndexes.size()); + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding); + } + } + } + + + static void ApplyVisitorToLeaf(DcmElement& element, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + Encoding encoding) + { + // TODO - Merge this function with ConvertLeafElement() + + assert(element.isLeaf()); + + DcmEVR evr = element.getTag().getEVR(); + ValueRepresentation vr = FromDcmtkBridge::Convert(evr); + + char *c = NULL; + if (element.isaString() && + element.getString(c).good()) + { + std::string utf8; + + if (c != NULL) // This case corresponds to the empty string + { + std::string s(c); + utf8 = Toolbox::ConvertToUtf8(s, encoding); + } + + std::string newValue; + ITagVisitor::Action action = visitor.VisitString + (newValue, parentTags, parentIndexes, tag, vr, utf8); + + switch (action) + { + case ITagVisitor::Action_None: + break; + + case ITagVisitor::Action_Replace: + { + std::string s = Toolbox::ConvertFromUtf8(newValue, encoding); + if (element.putString(s.c_str()) != EC_Normal) + { + LOG(ERROR) << "Cannot replace value of tag: " << tag.Format(); + throw OrthancException(ErrorCode_InternalError); + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return; // We're done + } + + + try + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + switch (element.getVR()) + { + + /** + * Deal with binary data (including PixelData). + **/ + + 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 + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + { + Uint8* data = NULL; + + if (element.getUint8Array(data) == EC_Normal) + { + visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength()); + } + else + { + visitor.VisitUnknown(parentTags, parentIndexes, tag, vr); + } + + break; + } + + /** + * Numeric types + **/ + + case EVR_SL: // signed long + { + Sint32 f; + if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_SS: // signed short + { + Sint16 f; + if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_UL: // unsigned long + { + Uint32 f; + if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_US: // unsigned short + { + Uint16 f; + if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_FL: // float single-precision + { + Float32 f; + if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good()) + { + visitor.VisitDouble(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_FD: // float double-precision + { + Float64 f; + if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good()) + { + visitor.VisitDouble(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + + /** + * Attribute tag. + **/ + + case EVR_AT: + { + DcmTagKey tagKey; + if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tagKey, 0).good()) + { + DicomTag t(tagKey.getGroup(), tagKey.getElement()); + visitor.VisitAttribute(parentTags, parentIndexes, tag, vr, t); + } + + break; + } + + + /** + * Sequence types, should never occur at this point because of + * "element.isLeaf()". + **/ + + case EVR_SQ: // sequence of items + return; + + + /** + * 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_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + visitor.VisitUnknown(parentTags, parentIndexes, tag, vr); + return; + + + /** + * Default case. + **/ + + default: + return; + } + } + catch (boost::bad_lexical_cast&) + { + return; + } + catch (std::bad_cast&) + { + return; + } + } + + + static void ApplyVisitorToElement(DcmElement& element, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + Encoding encoding) + { + assert(parentTags.size() == parentIndexes.size()); + + DicomTag tag(FromDcmtkBridge::Convert(element.getTag())); + + if (element.isLeaf()) + { + ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding); + } + else + { + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); + + std::vector<DicomTag> tags = parentTags; + std::vector<size_t> indexes = parentIndexes; + tags.push_back(tag); + indexes.push_back(0); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + indexes.back() = static_cast<size_t>(i); + DcmItem* child = sequence.getItem(i); + ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding); + } + } + } + + + void FromDcmtkBridge::Apply(DcmItem& dataset, + ITagVisitor& visitor, + Encoding defaultEncoding) + { + std::vector<DicomTag> parentTags; + std::vector<size_t> parentIndexes; + Encoding encoding = DetectEncoding(dataset, defaultEncoding); + ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/FromDcmtkBridge.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,249 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ITagVisitor.h" +#include "../DicomFormat/DicomElement.h" +#include "../DicomFormat/DicomMap.h" + +#include <dcmtk/dcmdata/dcdatset.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <json/json.h> + +#if !defined(ORTHANC_ENABLE_LUA) +# error The macro ORTHANC_ENABLE_LUA must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK != 1 +# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 +#endif + +#if ORTHANC_BUILD_UNIT_TESTS == 1 +# include <gtest/gtest_prod.h> +#endif + +#if ORTHANC_ENABLE_LUA == 1 +# include "../Lua/LuaFunctionCall.h" +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined +#endif + + +namespace Orthanc +{ + class FromDcmtkBridge : public boost::noncopyable + { +#if ORTHANC_BUILD_UNIT_TESTS == 1 + FRIEND_TEST(FromDcmtkBridge, FromJson); +#endif + + friend class ParsedDicomFile; + + private: + FromDcmtkBridge(); // Pure static class + + static void ExtractDicomSummary(DicomMap& target, + DcmItem& dataset, + unsigned int maxStringLength, + Encoding defaultEncoding); + + static void DatasetToJson(Json::Value& parent, + DcmItem& item, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + const std::set<DicomTag>& ignoreTagLength); + + static void ElementToJson(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding dicomEncoding, + const std::set<DicomTag>& ignoreTagLength); + + static void ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding defaultEncoding, + const std::set<DicomTag>& ignoreTagLength); + + static void ChangeStringEncoding(DcmItem& dataset, + Encoding source, + Encoding target); + + public: + static void InitializeDictionary(bool loadPrivateDictionary); + + static void RegisterDictionaryTag(const DicomTag& tag, + ValueRepresentation vr, + const std::string& name, + unsigned int minMultiplicity, + unsigned int maxMultiplicity, + const std::string& privateCreator); + + static Encoding DetectEncoding(DcmItem& dataset, + Encoding defaultEncoding); + + static DicomTag Convert(const DcmTag& tag); + + static DicomTag GetTag(const DcmElement& element); + + static bool IsUnknownTag(const DicomTag& tag); + + static DicomValue* ConvertLeafElement(DcmElement& element, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + const std::set<DicomTag>& ignoreTagLength); + + static void ExtractHeaderAsJson(Json::Value& target, + DcmMetaInfo& header, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength); + + static std::string GetTagName(const DicomTag& tag, + const std::string& privateCreator); + + static std::string GetTagName(const DcmElement& element); + + static std::string GetTagName(const DicomElement& element) + { + return GetTagName(element.GetTag(), ""); + } + + static DicomTag ParseTag(const char* name); + + static DicomTag ParseTag(const std::string& name) + { + return ParseTag(name.c_str()); + } + + static bool HasTag(const DicomMap& fields, + const std::string& tagName) + { + return fields.HasTag(ParseTag(tagName)); + } + + static const DicomValue& GetValue(const DicomMap& fields, + const std::string& tagName) + { + return fields.GetValue(ParseTag(tagName)); + } + + static void SetValue(DicomMap& target, + const std::string& tagName, + DicomValue* value) + { + target.SetValue(ParseTag(tagName), value); + } + + static void ToJson(Json::Value& result, + const DicomMap& values, + bool simplify); + + static std::string GenerateUniqueIdentifier(ResourceType level); + + static bool SaveToMemoryBuffer(std::string& buffer, + DcmDataset& dataSet); + + static ValueRepresentation Convert(DcmEVR vr); + + static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); + + static DcmElement* CreateElementForTag(const DicomTag& tag); + + static void FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& utf8alue, // Encoded using UTF-8 + bool decodeDataUriScheme, + Encoding dicomEncoding); + + static DcmElement* FromJson(const DicomTag& tag, + const Json::Value& element, // Encoded using UTF-8 + bool decodeDataUriScheme, + Encoding dicomEncoding); + + static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset); + + static Encoding ExtractEncoding(const Json::Value& json, + Encoding defaultEncoding); + + static DcmDataset* FromJson(const Json::Value& json, // Encoded using UTF-8 + bool generateIdentifiers, + bool decodeDataUriScheme, + Encoding defaultEncoding); + + static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer, + size_t size); + + static void FromJson(DicomMap& values, + const Json::Value& result); + + static bool LookupTransferSyntax(std::string& result, + DcmFileFormat& dicom); + +#if ORTHANC_ENABLE_LUA == 1 + static void ExecuteToDicom(DicomMap& target, + LuaFunctionCall& call); +#endif + + static void ExtractDicomSummary(DicomMap& target, + DcmItem& dataset); + + static void ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + const std::set<DicomTag>& ignoreTagLength); + + static void InitializeCodecs(); + + static void FinalizeCodecs(); + + static void Apply(DcmItem& dataset, + ITagVisitor& visitor, + Encoding defaultEncoding); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ITagVisitor.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,93 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../DicomFormat/DicomTag.h" + +#include <vector> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ITagVisitor : public boost::noncopyable + { + public: + enum Action + { + Action_Replace, + Action_None + }; + + virtual ~ITagVisitor() + { + } + + virtual void VisitUnknown(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr) = 0; + + virtual void VisitBinary(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const void* data, + size_t size) = 0; + + virtual void VisitInteger(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + int64_t value) = 0; + + virtual void VisitDouble(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + double value) = 0; + + virtual void VisitAttribute(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const DicomTag& value) = 0; + + virtual Action VisitString(std::string& newValue, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::string& value) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomFrameIndex.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,439 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomFrameIndex.h" + +#include "../../OrthancException.h" +#include "../../DicomFormat/DicomImageInformation.h" +#include "../FromDcmtkBridge.h" +#include "../../Endianness.h" +#include "DicomImageDecoder.h" + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcpxitem.h> +#include <dcmtk/dcmdata/dcpixseq.h> + +namespace Orthanc +{ + class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex + { + private: + DcmPixelSequence* pixelSequence_; + std::vector<DcmPixelItem*> startFragment_; + std::vector<unsigned int> countFragments_; + std::vector<unsigned int> frameSize_; + + void GetOffsetTable(std::vector<uint32_t>& table) + { + DcmPixelItem* item = NULL; + if (!pixelSequence_->getItem(item, 0).good() || + item == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + uint32_t length = item->getLength(); + if (length == 0) + { + table.clear(); + return; + } + + if (length % 4 != 0) + { + // Error: Each fragment is index with 4 bytes (uint32_t) + throw OrthancException(ErrorCode_BadFileFormat); + } + + uint8_t* content = NULL; + if (!item->getUint8Array(content).good() || + content == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + table.resize(length / 4); + + // The offset table is always in little endian in the DICOM + // file. Swap it to host endianness if needed. + const uint32_t* offset = reinterpret_cast<const uint32_t*>(content); + for (size_t i = 0; i < table.size(); i++, offset++) + { + table[i] = le32toh(*offset); + } + } + + + public: + FragmentIndex(DcmPixelSequence* pixelSequence, + unsigned int countFrames) : + pixelSequence_(pixelSequence) + { + assert(pixelSequence != NULL); + + startFragment_.resize(countFrames); + countFragments_.resize(countFrames); + frameSize_.resize(countFrames); + + // The first fragment corresponds to the offset table + unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card()); + if (countFragments < countFrames + 1) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + if (countFragments == countFrames + 1) + { + // Simple case: There is one fragment per frame. + + DcmObject* fragment = pixelSequence_->nextInContainer(NULL); // Skip the offset table + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + for (unsigned int i = 0; i < countFrames; i++) + { + fragment = pixelSequence_->nextInContainer(fragment); + startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment); + frameSize_[i] = fragment->getLength(); + countFragments_[i] = 1; + } + + return; + } + + // Parse the offset table + std::vector<uint32_t> offsetOfFrame; + GetOffsetTable(offsetOfFrame); + + if (offsetOfFrame.size() != countFrames || + offsetOfFrame[0] != 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + + // Loop over the fragments (ignoring the offset table). This is + // an alternative, faster implementation to DCMTK's + // "DcmCodec::determineStartFragment()". + DcmObject* fragment = pixelSequence_->nextInContainer(NULL); + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + uint32_t offset = 0; + unsigned int currentFrame = 0; + startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment); + + unsigned int currentFragment = 1; + while (fragment != NULL) + { + if (currentFrame + 1 < countFrames && + offset == offsetOfFrame[currentFrame + 1]) + { + currentFrame += 1; + startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment); + } + + frameSize_[currentFrame] += fragment->getLength(); + countFragments_[currentFrame]++; + + // 8 bytes = overhead for the item tag and length field + offset += fragment->getLength() + 8; + + currentFragment++; + fragment = pixelSequence_->nextInContainer(fragment); + } + + if (currentFragment != countFragments || + currentFrame + 1 != countFrames || + fragment != NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + assert(startFragment_.size() == countFragments_.size() && + startFragment_.size() == frameSize_.size()); + } + + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + if (index >= startFragment_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + frame.resize(frameSize_[index]); + if (frame.size() == 0) + { + return; + } + + uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]); + + size_t offset = 0; + DcmPixelItem* fragment = startFragment_[index]; + for (unsigned int i = 0; i < countFragments_[index]; i++) + { + uint8_t* content = NULL; + if (!fragment->getUint8Array(content).good() || + content == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + assert(offset + fragment->getLength() <= frame.size()); + + memcpy(target + offset, content, fragment->getLength()); + offset += fragment->getLength(); + + fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment)); + } + } + }; + + + + class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex + { + private: + uint8_t* pixelData_; + size_t frameSize_; + + public: + UncompressedIndex(DcmDataset& dataset, + unsigned int countFrames, + size_t frameSize) : + pixelData_(NULL), + frameSize_(frameSize) + { + size_t size = 0; + + DcmElement* e; + if (dataset.findAndGetElement(DCM_PixelData, e).good() && + e != NULL) + { + size = e->getLength(); + + if (size > 0) + { + pixelData_ = NULL; + if (!e->getUint8Array(pixelData_).good() || + pixelData_ == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + } + + if (size < frameSize_ * countFrames) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + frame.resize(frameSize_); + if (frameSize_ > 0) + { + memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); + } + } + }; + + + class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex + { + private: + std::string pixelData_; + size_t frameSize_; + + public: + PsmctRle1Index(DcmDataset& dataset, + unsigned int countFrames, + size_t frameSize) : + frameSize_(frameSize) + { + if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) || + pixelData_.size() < frameSize * countFrames) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + frame.resize(frameSize_); + if (frameSize_ > 0) + { + memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_); + } + } + }; + + + + bool DicomFrameIndex::IsVideo(DcmFileFormat& 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() || + tmp == NULL) + { + return 1; + } + + int count = -1; + try + { + count = boost::lexical_cast<int>(tmp); + } + catch (boost::bad_lexical_cast&) + { + } + + if (count < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return count; + } + } + + + DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom) + { + countFrames_ = GetFramesCount(dicom); + if (countFrames_ == 0) + { + // The image has no frame. No index is to be built. + return; + } + + DcmDataset& dataset = *dicom.getDataset(); + + // Test whether this image is composed of a sequence of fragments + DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); + if (pixelSequence != NULL) + { + index_.reset(new FragmentIndex(pixelSequence, countFrames_)); + return; + } + + // Extract information about the image structure + DicomMap tags; + FromDcmtkBridge::ExtractDicomSummary(tags, dataset); + + DicomImageInformation information(tags); + + // Access to the raw pixel data + if (DicomImageDecoder::IsPsmctRle1(dataset)) + { + index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize())); + } + else + { + index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize())); + } + } + + + void DicomFrameIndex::GetRawFrame(std::string& frame, + unsigned int index) const + { + if (index >= countFrames_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else if (index_.get() != NULL) + { + return index_->GetRawFrame(frame, index); + } + else + { + frame.clear(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomFrameIndex.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Enumerations.h" + +#include <dcmtk/dcmdata/dcdatset.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <vector> +#include <stdint.h> +#include <boost/noncopyable.hpp> +#include <memory> + +namespace Orthanc +{ + class DicomFrameIndex + { + private: + class IIndex : public boost::noncopyable + { + public: + virtual ~IIndex() + { + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const = 0; + }; + + class FragmentIndex; + class UncompressedIndex; + class PsmctRle1Index; + + std::auto_ptr<IIndex> index_; + unsigned int countFrames_; + + public: + DicomFrameIndex(DcmFileFormat& dicom); + + unsigned int GetFramesCount() const + { + return countFrames_; + } + + void GetRawFrame(std::string& frame, + unsigned int index) const; + + static bool IsVideo(DcmFileFormat& dicom); + + static unsigned int GetFramesCount(DcmFileFormat& dicom); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1001 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomImageDecoder.h" + + +/*========================================================================= + + This file is based on portions of the following project + (cf. function "DecodePsmctRle1()"): + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + + Copyright (c) 2006-2011 Mathieu Malaterre + Copyright (c) 1993-2005 CREATIS + (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + =========================================================================*/ + + +#include "../../Logging.h" +#include "../../OrthancException.h" +#include "../../Images/Image.h" +#include "../../Images/ImageProcessing.h" +#include "../../DicomFormat/DicomIntegerPixelAccessor.h" +#include "../ToDcmtkBridge.h" +#include "../FromDcmtkBridge.h" +#include "../ParsedDicomFile.h" + +#if ORTHANC_ENABLE_PNG == 1 +# include "../../Images/PngWriter.h" +#endif + +#if ORTHANC_ENABLE_JPEG == 1 +# include "../../Images/JpegWriter.h" +#endif +#include "../../Images/PamWriter.h" + +#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> + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 +# include <dcmtk/dcmjpeg/djrplol.h> +# include <dcmtk/dcmjpls/djcodecd.h> +# include <dcmtk/dcmjpls/djcparam.h> +# include <dcmtk/dcmjpls/djrparam.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 +# include <dcmtk/dcmjpeg/djcodecd.h> +# include <dcmtk/dcmjpeg/djcparam.h> +# include <dcmtk/dcmjpeg/djdecbas.h> +# include <dcmtk/dcmjpeg/djdecext.h> +# include <dcmtk/dcmjpeg/djdeclol.h> +# include <dcmtk/dcmjpeg/djdecpro.h> +# include <dcmtk/dcmjpeg/djdecsps.h> +# include <dcmtk/dcmjpeg/djdecsv1.h> +# include <dcmtk/dcmjpeg/djrploss.h> +#endif + +#if DCMTK_VERSION_NUMBER <= 360 +# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax +# define EXS_JPEGProcess2_4 EXS_JPEGProcess2_4TransferSyntax +# define EXS_JPEGProcess6_8 EXS_JPEGProcess6_8TransferSyntax +# define EXS_JPEGProcess10_12 EXS_JPEGProcess10_12TransferSyntax +# define EXS_JPEGProcess14 EXS_JPEGProcess14TransferSyntax +# define EXS_JPEGProcess14SV1 EXS_JPEGProcess14SV1TransferSyntax +#endif + +namespace Orthanc +{ + static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); + static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); + + + bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset) + { + DcmElement* e; + char* c; + + // Check whether the DICOM instance contains an image encoded with + // the PMSCT_RLE1 scheme. + if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() || + e == NULL || + !e->isaString() || + !e->getString(c).good() || + c == NULL || + strcmp("PMSCT_RLE1", c)) + { + return false; + } + else + { + return true; + } + } + + + bool DicomImageDecoder::DecodePsmctRle1(std::string& output, + DcmDataset& dataset) + { + // Check whether the DICOM instance contains an image encoded with + // the PMSCT_RLE1 scheme. + if (!IsPsmctRle1(dataset)) + { + return false; + } + + // OK, this is a custom RLE encoding from Philips. Get the pixel + // data from the appropriate private DICOM tag. + Uint8* pixData = NULL; + DcmElement* e; + if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() || + e == NULL || + e->getUint8Array(pixData) != EC_Normal) + { + return false; + } + + // The "unsigned" below IS VERY IMPORTANT + const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData); + const size_t length = e->getLength(); + + /** + * The code below is an adaptation of a sample code for GDCM by + * Mathieu Malaterre (under a BSD license). + * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html + **/ + + // RLE pass + std::vector<uint8_t> temp; + temp.reserve(length); + for (size_t i = 0; i < length; i++) + { + if (inbuffer[i] == 0xa5) + { + temp.push_back(inbuffer[i+2]); + for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) + { + temp.push_back(inbuffer[i+2]); + } + i += 2; + } + else + { + temp.push_back(inbuffer[i]); + } + } + + // Delta encoding pass + uint16_t delta = 0; + output.clear(); + output.reserve(temp.size()); + for (size_t i = 0; i < temp.size(); i++) + { + uint16_t value; + + if (temp[i] == 0x5a) + { + uint16_t v1 = temp[i + 1]; + uint16_t v2 = temp[i + 2]; + value = (v2 << 8) + v1; + i += 2; + } + else + { + value = delta + (int8_t) temp[i]; + } + + output.push_back(value & 0xff); + output.push_back(value >> 8); + delta = value; + } + + if (output.size() % 2) + { + output.resize(output.size() - 1); + } + + return true; + } + + + class DicomImageDecoder::ImageSource + { + private: + std::string psmct_; + std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_; + + public: + void Setup(DcmDataset& dataset, + unsigned int frame) + { + psmct_.clear(); + slowAccessor_.reset(NULL); + + // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data + + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, dataset); + + /** + * Create an accessor to the raw values of the DICOM image. + **/ + + DcmElement* e; + if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && + e != NULL) + { + Uint8* pixData = NULL; + if (e->getUint8Array(pixData) == EC_Normal) + { + slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); + } + } + else if (DecodePsmctRle1(psmct_, dataset)) + { + LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; + Uint8* pixData = NULL; + if (psmct_.size() > 0) + { + pixData = reinterpret_cast<Uint8*>(&psmct_[0]); + } + + slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size())); + } + + if (slowAccessor_.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + slowAccessor_->SetCurrentFrame(frame); + } + + unsigned int GetWidth() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetWidth(); + } + + unsigned int GetHeight() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetHeight(); + } + + unsigned int GetChannelCount() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetChannelCount(); + } + + const DicomIntegerPixelAccessor& GetAccessor() const + { + assert(slowAccessor_.get() != NULL); + return *slowAccessor_; + } + + unsigned int GetSize() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetSize(); + } + }; + + + ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset, + bool ignorePhotometricInterpretation) + { + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, dataset); + + DicomImageInformation info(m); + PixelFormat format; + + if (!info.ExtractPixelFormat(format, ignorePhotometricInterpretation)) + { + LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() + << "bpp, " << info.GetChannelCount() << " channels, " + << (info.IsSigned() ? "signed" : "unsigned") + << (info.IsPlanar() ? ", planar, " : ", non-planar, ") + << EnumerationToString(info.GetPhotometricInterpretation()) + << " photometric interpretation"; + throw OrthancException(ErrorCode_NotImplemented); + } + + return new Image(format, info.GetWidth(), info.GetHeight(), false); + } + + + template <typename PixelType> + static void CopyPixels(ImageAccessor& target, + const DicomIntegerPixelAccessor& source) + { + const PixelType minValue = std::numeric_limits<PixelType>::min(); + const PixelType maxValue = std::numeric_limits<PixelType>::max(); + + for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++) + { + PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++) + { + for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++) + { + int32_t v = source.GetValue(x, y, c); + if (v < static_cast<int32_t>(minValue)) + { + *pixel = minValue; + } + else if (v > static_cast<int32_t>(maxValue)) + { + *pixel = maxValue; + } + else + { + *pixel = static_cast<PixelType>(v); + } + } + } + } + } + + + static ImageAccessor* DecodeLookupTable(std::auto_ptr<ImageAccessor>& target, + const DicomImageInformation& info, + DcmDataset& dataset, + const uint8_t* pixelData, + unsigned long pixelLength) + { + LOG(INFO) << "Decoding a lookup table"; + + OFString r, g, b; + PixelFormat format; + const uint16_t* lutRed = NULL; + const uint16_t* lutGreen = NULL; + const uint16_t* lutBlue = NULL; + unsigned long rc = 0; + unsigned long gc = 0; + unsigned long bc = 0; + + if (pixelData == NULL && + !dataset.findAndGetUint8Array(DCM_PixelData, pixelData, &pixelLength).good()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if (info.IsPlanar() || + info.GetNumberOfFrames() != 1 || + !info.ExtractPixelFormat(format, false) || + !dataset.findAndGetOFStringArray(DCM_BluePaletteColorLookupTableDescriptor, b).good() || + !dataset.findAndGetOFStringArray(DCM_GreenPaletteColorLookupTableDescriptor, g).good() || + !dataset.findAndGetOFStringArray(DCM_RedPaletteColorLookupTableDescriptor, r).good() || + !dataset.findAndGetUint16Array(DCM_BluePaletteColorLookupTableData, lutBlue, &bc).good() || + !dataset.findAndGetUint16Array(DCM_GreenPaletteColorLookupTableData, lutGreen, &gc).good() || + !dataset.findAndGetUint16Array(DCM_RedPaletteColorLookupTableData, lutRed, &rc).good() || + r != g || + r != b || + g != b || + lutRed == NULL || + lutGreen == NULL || + lutBlue == NULL || + pixelData == NULL) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + switch (format) + { + case PixelFormat_RGB24: + { + if (r != "256\\0\\16" || + rc != 256 || + gc != 256 || + bc != 256 || + pixelLength != target->GetWidth() * target->GetHeight()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData); + + for (unsigned int y = 0; y < target->GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(target->GetRow(y)); + + for (unsigned int x = 0; x < target->GetWidth(); x++) + { + p[0] = lutRed[*source] >> 8; + p[1] = lutGreen[*source] >> 8; + p[2] = lutBlue[*source] >> 8; + source++; + p += 3; + } + } + + return target.release(); + } + + case PixelFormat_RGB48: + { + if (r != "0\\0\\16" || + rc != 65536 || + gc != 65536 || + bc != 65536 || + pixelLength != 2 * target->GetWidth() * target->GetHeight()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + const uint16_t* source = reinterpret_cast<const uint16_t*>(pixelData); + + for (unsigned int y = 0; y < target->GetHeight(); y++) + { + uint16_t* p = reinterpret_cast<uint16_t*>(target->GetRow(y)); + + for (unsigned int x = 0; x < target->GetWidth(); x++) + { + p[0] = lutRed[*source]; + p[1] = lutGreen[*source]; + p[2] = lutBlue[*source]; + source++; + p += 3; + } + } + + return target.release(); + } + + default: + break; + } + + throw OrthancException(ErrorCode_InternalError); + } + + + ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, + unsigned int frame) + { + /** + * Create the target image. + **/ + + std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false)); + + ImageSource source; + source.Setup(dataset, frame); + + if (source.GetWidth() != target->GetWidth() || + source.GetHeight() != target->GetHeight()) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * Deal with lookup tables + **/ + + const DicomImageInformation& info = source.GetAccessor().GetInformation(); + + if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette) + { + return DecodeLookupTable(target, info, dataset, NULL, 0); + } + + + /** + * If the format of the DICOM buffer is natively supported, use a + * direct access to copy its values. + **/ + + bool fastVersionSuccess = false; + PixelFormat sourceFormat; + if (!info.IsPlanar() && + info.ExtractPixelFormat(sourceFormat, false)) + { + try + { + size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat); + if ((frame + 1) * frameSize <= source.GetSize()) + { + const uint8_t* buffer = reinterpret_cast<const uint8_t*>(source.GetAccessor().GetPixelData()); + + ImageAccessor sourceImage; + sourceImage.AssignReadOnly(sourceFormat, + info.GetWidth(), + info.GetHeight(), + info.GetWidth() * GetBytesPerPixel(sourceFormat), + buffer + frame * frameSize); + + ImageProcessing::Convert(*target, sourceImage); + ImageProcessing::ShiftRight(*target, info.GetShift()); + fastVersionSuccess = true; + } + } + catch (OrthancException&) + { + // Unsupported conversion, use the slow version + } + } + + /** + * Slow version : loop over the DICOM buffer, storing its value + * into the target image. + **/ + + if (!fastVersionSuccess) + { + switch (target->GetFormat()) + { + case PixelFormat_RGB24: + case PixelFormat_RGBA32: + case PixelFormat_Grayscale8: + CopyPixels<uint8_t>(*target, source.GetAccessor()); + break; + + case PixelFormat_Grayscale16: + CopyPixels<uint16_t>(*target, source.GetAccessor()); + break; + + case PixelFormat_SignedGrayscale16: + CopyPixels<int16_t>(*target, source.GetAccessor()); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + return target.release(); + } + + + ImageAccessor* DicomImageDecoder::ApplyCodec + (const DcmCodec& codec, + const DcmCodecParameter& parameters, + const DcmRepresentationParameter& representationParameter, + DcmDataset& dataset, + unsigned int frame) + { + DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); + if (pixelSequence == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, dataset); + DicomImageInformation info(m); + + std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true)); + + Uint32 startFragment = 0; // Default + OFString decompressedColorModel; // Out + + OFCondition c; + + if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette && + info.GetChannelCount() == 1) + { + std::string uncompressed; + uncompressed.resize(info.GetWidth() * info.GetHeight() * info.GetBytesPerValue()); + + if (uncompressed.size() == 0 || + !codec.decodeFrame(&representationParameter, + pixelSequence, ¶meters, + &dataset, frame, startFragment, &uncompressed[0], + uncompressed.size(), decompressedColorModel).good()) + { + LOG(ERROR) << "Cannot decode a palette image"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + return DecodeLookupTable(target, info, dataset, + reinterpret_cast<const uint8_t*>(uncompressed.c_str()), + uncompressed.size()); + } + else + { + if (!codec.decodeFrame(&representationParameter, + pixelSequence, ¶meters, + &dataset, frame, startFragment, target->GetBuffer(), + target->GetSize(), decompressedColorModel).good()) + { + LOG(ERROR) << "Cannot decode a non-palette image"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + return target.release(); + } + } + + + ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, + unsigned int frame) + { + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + E_TransferSyntax syntax = dataset.getOriginalXfer(); + + /** + * Deal with uncompressed, raw images. + * http://support.dcmtk.org/docs/dcxfer_8h-source.html + **/ + if (syntax == EXS_Unknown || + syntax == EXS_LittleEndianImplicit || + syntax == EXS_BigEndianImplicit || + syntax == EXS_LittleEndianExplicit || + syntax == EXS_BigEndianExplicit) + { + return DecodeUncompressedImage(dataset, frame); + } + + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + /** + * Deal with JPEG-LS images. + **/ + + if (syntax == EXS_JPEGLSLossless || + syntax == EXS_JPEGLSLossy) + { + // The (2, OFTrue) are the default parameters as found in DCMTK 3.6.2 + // http://support.dcmtk.org/docs/classDJLSRepresentationParameter.html + DJLSRepresentationParameter representationParameter(2, OFTrue); + + DJLSCodecParameter parameters; + std::auto_ptr<DJLSDecoderBase> decoder; + + switch (syntax) + { + case EXS_JPEGLSLossless: + LOG(INFO) << "Decoding a JPEG-LS lossless DICOM image"; + decoder.reset(new DJLSLosslessDecoder); + break; + + case EXS_JPEGLSLossy: + LOG(INFO) << "Decoding a JPEG-LS near-lossless DICOM image"; + decoder.reset(new DJLSNearLosslessDecoder); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); + } +#endif + + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + /** + * Deal with JPEG images. + **/ + + if (syntax == EXS_JPEGProcess1 || // DJDecoderBaseline + syntax == EXS_JPEGProcess2_4 || // DJDecoderExtended + syntax == EXS_JPEGProcess6_8 || // DJDecoderSpectralSelection (retired) + syntax == EXS_JPEGProcess10_12 || // DJDecoderProgressive (retired) + syntax == EXS_JPEGProcess14 || // DJDecoderLossless + syntax == EXS_JPEGProcess14SV1) // DJDecoderP14SV1 + { + // http://support.dcmtk.org/docs-snapshot/djutils_8h.html#a2a9695e5b6b0f5c45a64c7f072c1eb9d + DJCodecParameter parameters( + ECC_lossyYCbCr, // Mode for color conversion for compression, Unused for decompression + EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr + 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; + + switch (syntax) + { + case EXS_JPEGProcess1: + LOG(INFO) << "Decoding a JPEG baseline (process 1) DICOM image"; + decoder.reset(new DJDecoderBaseline); + break; + + case EXS_JPEGProcess2_4 : + LOG(INFO) << "Decoding a JPEG baseline (processes 2 and 4) DICOM image"; + decoder.reset(new DJDecoderExtended); + break; + + case EXS_JPEGProcess6_8: // Retired + LOG(INFO) << "Decoding a JPEG spectral section, nonhierarchical (processes 6 and 8) DICOM image"; + decoder.reset(new DJDecoderSpectralSelection); + break; + + case EXS_JPEGProcess10_12: // Retired + LOG(INFO) << "Decoding a JPEG full progression, nonhierarchical (processes 10 and 12) DICOM image"; + decoder.reset(new DJDecoderProgressive); + break; + + case EXS_JPEGProcess14: + LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical (process 14) DICOM image"; + decoder.reset(new DJDecoderLossless); + break; + + case EXS_JPEGProcess14SV1: + LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical, first-order prediction (process 14 selection value 1) DICOM image"; + decoder.reset(new DJDecoderP14SV1); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); + } +#endif + + + if (syntax == EXS_RLELossless) + { + LOG(INFO) << "Decoding a RLE lossless DICOM image"; + DcmRLECodecParameter parameters; + DcmRLECodecDecoder decoder; + DcmRLERepresentationParameter representationParameter; + return ApplyCodec(decoder, parameters, representationParameter, dataset, frame); + } + + + /** + * This DICOM image format is not natively supported by + * Orthanc. As a last resort, try and decode it through DCMTK by + * converting its transfer syntax to Little Endian. This will + * result in higher memory consumption. This is actually the + * second example of the following page: + * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples + **/ + + { + LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian"; + + std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); + converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); + + if (converted->canWriteXfer(EXS_LittleEndianExplicit)) + { + return DecodeUncompressedImage(*converted, frame); + } + } + + LOG(ERROR) << "Cannot decode a DICOM image with the built-in decoder"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + + static bool IsColorImage(PixelFormat format) + { + return (format == PixelFormat_RGB24 || + format == PixelFormat_RGBA32); + } + + + bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, + PixelFormat format, + bool allowColorConversion) + { + // If specified, prevent the conversion between color and + // grayscale images + bool isSourceColor = IsColorImage(image->GetFormat()); + bool isTargetColor = IsColorImage(format); + + if (!allowColorConversion) + { + if (isSourceColor ^ isTargetColor) + { + return false; + } + } + + if (image->GetFormat() != format) + { + // A conversion is required + std::auto_ptr<ImageAccessor> target + (new Image(format, image->GetWidth(), image->GetHeight(), false)); + ImageProcessing::Convert(*target, *image); + image = target; + } + + return true; + } + + + bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image) + { + switch (image->GetFormat()) + { + case PixelFormat_RGB24: + { + // Directly return color images without modification (RGB) + return true; + } + + case PixelFormat_RGB48: + { + std::auto_ptr<ImageAccessor> target + (new Image(PixelFormat_RGB24, image->GetWidth(), image->GetHeight(), false)); + ImageProcessing::Convert(*target, *image); + image = target; + return true; + } + + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + // Grayscale image: Stretch its dynamics to the [0,255] range + int64_t a, b; + ImageProcessing::GetMinMaxIntegerValue(a, b, *image); + + if (a == b) + { + ImageProcessing::Set(*image, 0); + } + else + { + ImageProcessing::ShiftScale(*image, static_cast<float>(-a), + 255.0f / static_cast<float>(b - a), + true /* TODO - Consider using "false" to speed up */); + } + + // If the source image is not grayscale 8bpp, convert it + if (image->GetFormat() != PixelFormat_Grayscale8) + { + std::auto_ptr<ImageAccessor> target + (new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); + ImageProcessing::Convert(*target, *image); + image = target; + } + + return true; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert) + { + if (image.get() == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + bool ok = false; + + switch (mode) + { + case ImageExtractionMode_UInt8: + ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false); + break; + + case ImageExtractionMode_UInt16: + ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false); + break; + + case ImageExtractionMode_Int16: + ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false); + break; + + case ImageExtractionMode_Preview: + ok = PreviewDecodedImage(image); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (ok) + { + assert(image.get() != NULL); + + if (invert) + { + Orthanc::ImageProcessing::Invert(*image); + } + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void DicomImageDecoder::ExtractPamImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert) + { + ApplyExtractionMode(image, mode, invert); + + PamWriter writer; + writer.WriteToMemory(result, *image); + } + +#if ORTHANC_ENABLE_PNG == 1 + void DicomImageDecoder::ExtractPngImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert) + { + ApplyExtractionMode(image, mode, invert); + + PngWriter writer; + writer.WriteToMemory(result, *image); + } +#endif + + +#if ORTHANC_ENABLE_JPEG == 1 + void DicomImageDecoder::ExtractJpegImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert, + uint8_t quality) + { + if (mode != ImageExtractionMode_UInt8 && + mode != ImageExtractionMode_Preview) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + ApplyExtractionMode(image, mode, invert); + + JpegWriter writer; + writer.SetQuality(quality); + writer.WriteToMemory(result, *image); + } +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "../ParsedDicomFile.h" + +#include <memory> + +#if !defined(ORTHANC_ENABLE_JPEG) +# error The macro ORTHANC_ENABLE_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_PNG) +# error The macro ORTHANC_ENABLE_PNG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined +#endif + + +class DcmDataset; +class DcmCodec; +class DcmCodecParameter; +class DcmRepresentationParameter; + +namespace Orthanc +{ + class DicomImageDecoder : public boost::noncopyable + { + private: + class ImageSource; + + DicomImageDecoder() // This is a fully abstract class, no constructor + { + } + + static ImageAccessor* CreateImage(DcmDataset& dataset, + bool ignorePhotometricInterpretation); + + static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset, + unsigned int frame); + + static ImageAccessor* ApplyCodec(const DcmCodec& codec, + const DcmCodecParameter& parameters, + const DcmRepresentationParameter& representationParameter, + DcmDataset& dataset, + unsigned int frame); + + static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, + PixelFormat format, + bool allowColorConversion); + + static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image); + + static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert); + + public: + static bool IsPsmctRle1(DcmDataset& dataset); + + static bool DecodePsmctRle1(std::string& output, + DcmDataset& dataset); + + static ImageAccessor *Decode(ParsedDicomFile& dicom, + unsigned int frame); + + static void ExtractPamImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert); + +#if ORTHANC_ENABLE_PNG == 1 + static void ExtractPngImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert); +#endif + +#if ORTHANC_ENABLE_JPEG == 1 + static void ExtractJpegImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert, + uint8_t quality); +#endif + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ParsedDicomFile.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1549 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 based on portions of the following project: + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + + Copyright (c) 2006-2011 Mathieu Malaterre + Copyright (c) 1993-2005 CREATIS + (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + =========================================================================*/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "ParsedDicomFile.h" + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" +#include "Internals/DicomFrameIndex.h" +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + +#if ORTHANC_ENABLE_JPEG == 1 +# include "../Images/JpegReader.h" +#endif + +#if ORTHANC_ENABLE_PNG == 1 +# include "../Images/PngReader.h" +#endif + +#include <list> +#include <limits> + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcchrstr.h> +#include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> + +#include <dcmtk/dcmdata/dcvrae.h> +#include <dcmtk/dcmdata/dcvras.h> +#include <dcmtk/dcmdata/dcvrcs.h> +#include <dcmtk/dcmdata/dcvrda.h> +#include <dcmtk/dcmdata/dcvrds.h> +#include <dcmtk/dcmdata/dcvrdt.h> +#include <dcmtk/dcmdata/dcvrfd.h> +#include <dcmtk/dcmdata/dcvrfl.h> +#include <dcmtk/dcmdata/dcvris.h> +#include <dcmtk/dcmdata/dcvrlo.h> +#include <dcmtk/dcmdata/dcvrlt.h> +#include <dcmtk/dcmdata/dcvrpn.h> +#include <dcmtk/dcmdata/dcvrsh.h> +#include <dcmtk/dcmdata/dcvrsl.h> +#include <dcmtk/dcmdata/dcvrss.h> +#include <dcmtk/dcmdata/dcvrst.h> +#include <dcmtk/dcmdata/dcvrtm.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcvrul.h> +#include <dcmtk/dcmdata/dcvrus.h> +#include <dcmtk/dcmdata/dcvrut.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcpxitem.h> + + +#include <boost/math/special_functions/round.hpp> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <boost/algorithm/string/predicate.hpp> + + +#if DCMTK_VERSION_NUMBER <= 360 +# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax +#endif + + + +namespace Orthanc +{ + struct ParsedDicomFile::PImpl + { + std::auto_ptr<DcmFileFormat> file_; + std::auto_ptr<DicomFrameIndex> frameIndex_; + }; + + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 + static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; + + static void ParseTagAndGroup(DcmTagKey& key, + const std::string& tag) + { + DicomTag t = FromDcmtkBridge::ParseTag(tag); + key = DcmTagKey(t.GetGroup(), t.GetElement()); + } + + + static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, + E_TransferSyntax transferSyntax) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + return pixelSequence->card(); + } + else + { + return 1; + } + } + + + static void SendPathValueForDictionary(RestApiOutput& output, + DcmItem& dicom) + { + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < dicom.card(); i++) + { + DcmElement* element = dicom.getElement(i); + if (element) + { + char buf[16]; + sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); + v.append(buf); + } + } + + output.AnswerJson(v); + } + + + static void SendSequence(RestApiOutput& output, + DcmSequenceOfItems& sequence) + { + // This element is a sequence + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < sequence.card(); i++) + { + v.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(v); + } + + + namespace + { + class DicomFieldStream : public IHttpStreamAnswer + { + private: + DcmElement& element_; + uint32_t length_; + uint32_t offset_; + std::string chunk_; + size_t chunkSize_; + + public: + DicomFieldStream(DcmElement& element, + E_TransferSyntax transferSyntax) : + element_(element), + length_(element.getLength(transferSyntax)), + offset_(0), + chunkSize_(0) + { + static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB + chunk_.resize(CHUNK_SIZE); + } + + virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, + bool /*deflateAllowed*/) + { + // No support for compression + return HttpCompression_None; + } + + virtual bool HasContentFilename(std::string& filename) + { + return false; + } + + virtual std::string GetContentType() + { + return ""; + } + + virtual uint64_t GetContentLength() + { + return length_; + } + + virtual bool ReadNextChunk() + { + assert(offset_ <= length_); + + if (offset_ == length_) + { + return false; + } + else + { + if (length_ - offset_ < chunk_.size()) + { + chunkSize_ = length_ - offset_; + } + else + { + chunkSize_ = chunk_.size(); + } + + OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_); + + offset_ += chunkSize_; + + if (!cond.good()) + { + LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); + throw OrthancException(ErrorCode_InternalError); + } + + return true; + } + } + + virtual const char *GetChunkContent() + { + return chunk_.c_str(); + } + + virtual size_t GetChunkSize() + { + return chunkSize_; + } + }; + } + + + static bool AnswerPixelData(RestApiOutput& output, + DcmItem& dicom, + E_TransferSyntax transferSyntax, + const std::string* blockUri) + { + DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + DcmElement *element = NULL; + if (!dicom.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + try + { + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + if (blockUri == NULL) + { + // The user asks how many blocks are present in this pixel data + unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); + + Json::Value result(Json::arrayValue); + for (unsigned int i = 0; i < blocks; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(result); + return true; + } + + + unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); + + if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + // This is the case for JPEG transfer syntaxes + if (block < pixelSequence->card()) + { + DcmPixelItem* pixelItem = NULL; + if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) + { + if (pixelItem->getLength() == 0) + { + output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); + return true; + } + + Uint8* buffer = NULL; + if (pixelItem->getUint8Array(buffer).good() && buffer) + { + output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); + return true; + } + } + } + } + else + { + // This is the case for raw, uncompressed image buffers + assert(*blockUri == "0"); + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); + } + } + } + catch (boost::bad_lexical_cast&) + { + // The URI entered by the user is not a number + } + catch (std::bad_cast&) + { + // This should never happen + } + + return false; + } + + + static void SendPathValueForLeaf(RestApiOutput& output, + const std::string& tag, + DcmItem& dicom, + E_TransferSyntax transferSyntax) + { + DcmTagKey k; + ParseTagAndGroup(k, tag); + + DcmSequenceOfItems* sequence = NULL; + if (dicom.findAndGetSequence(k, sequence).good() && + sequence != NULL && + sequence->getVR() == EVR_SQ) + { + SendSequence(output, *sequence); + return; + } + + DcmElement* element = NULL; + if (dicom.findAndGetElement(k, element).good() && + element != NULL && + //element->getVR() != EVR_UNKNOWN && // This would forbid private tags + element->getVR() != EVR_SQ) + { + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); + } + } +#endif + + + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 + void ParsedDicomFile::SendPathValue(RestApiOutput& output, + const UriComponents& uri) + { + DcmItem* dicom = pimpl_->file_->getDataset(); + E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); + + // Special case: Accessing the pixel data + if (uri.size() == 1 || + uri.size() == 2) + { + DcmTagKey tag; + ParseTagAndGroup(tag, uri[0]); + + if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && + tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) + { + AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); + return; + } + } + + // Go down in the tag hierarchy according to the URI + for (size_t pos = 0; pos < uri.size() / 2; pos++) + { + size_t index; + try + { + index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + DcmTagKey k; + DcmItem *child = NULL; + ParseTagAndGroup(k, uri[2 * pos]); + if (!dicom->findAndGetSequenceItem(k, child, index).good() || + child == NULL) + { + return; + } + + dicom = child; + } + + // We have reached the end of the URI + if (uri.size() % 2 == 0) + { + SendPathValueForDictionary(output, *dicom); + } + else + { + SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); + } + } +#endif + + + void ParsedDicomFile::Remove(const DicomTag& tag) + { + InvalidateCache(); + + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = pimpl_->file_->getDataset()->remove(key); + if (element != NULL) + { + delete element; + } + } + + + void ParsedDicomFile::Clear(const DicomTag& tag, + bool onlyIfExists) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + InvalidateCache(); + + DcmItem* dicom = pimpl_->file_->getDataset(); + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + + if (onlyIfExists && + !dicom->tagExists(key)) + { + // The tag is non-existing, do not clear it + } + else + { + if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep) + { + InvalidateCache(); + + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + // Loop over the dataset to detect its private tags + typedef std::list<DcmElement*> Tags; + Tags privateTags; + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + DcmTag tag(element->getTag()); + + // Is this a private tag? + if (tag.isPrivate()) + { + bool remove = true; + + // Check whether this private tag is to be kept + if (toKeep != NULL) + { + DicomTag tmp = FromDcmtkBridge::Convert(tag); + if (toKeep->find(tmp) != toKeep->end()) + { + remove = false; // Keep it + } + } + + if (remove) + { + privateTags.push_back(element); + } + } + } + + // Loop over the detected private tags to remove them + for (Tags::iterator it = privateTags.begin(); + it != privateTags.end(); ++it) + { + DcmElement* tmp = dataset.remove(*it); + if (tmp != NULL) + { + delete tmp; + } + } + } + + + static void InsertInternal(DcmDataset& dicom, + DcmElement* element) + { + OFCondition cond = dicom.insert(element, false, false); + if (!cond.good()) + { + // This field already exists + delete element; + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::Insert(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag))) + { + throw OrthancException(ErrorCode_AlreadyExistingTag); + } + + if (decodeDataUriScheme && + value.type() == Json::stringValue && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(value.asString())) + { + return; + } + } + + InvalidateCache(); + std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); + InsertInternal(*pimpl_->file_->getDataset(), element.release()); + } + + + static bool CanReplaceProceed(DcmDataset& dicom, + const DcmTagKey& tag, + DicomReplaceMode mode) + { + if (dicom.findAndDeleteElement(tag).good()) + { + // This tag was existing, it has been deleted + return true; + } + else + { + // This tag was absent, act wrt. the specified "mode" + switch (mode) + { + case DicomReplaceMode_InsertIfAbsent: + return true; + + case DicomReplaceMode_ThrowIfAbsent: + throw OrthancException(ErrorCode_InexistentItem); + + case DicomReplaceMode_IgnoreIfAbsent: + return false; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } + + + void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme) + { + if (tag != DICOM_TAG_SOP_CLASS_UID && + tag != DICOM_TAG_SOP_INSTANCE_UID) + { + return; + } + + std::string binary; + const std::string* decoded = &utf8Value; + + if (decodeDataUriScheme && + boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) + { + std::string mime; + if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + decoded = &binary; + } + else + { + Encoding encoding = GetEncoding(); + if (GetEncoding() != Encoding_Utf8) + { + binary = Toolbox::ConvertFromUtf8(utf8Value, encoding); + decoded = &binary; + } + } + + /** + * dcmodify will automatically correct 'Media Storage SOP Class + * UID' and 'Media Storage SOP Instance UID' in the metaheader, if + * you make changes to the related tags in the dataset ('SOP Class + * UID' and 'SOP Instance UID') via insert or modify mode + * options. You can disable this behaviour by using the -nmu + * option. + **/ + + if (tag == DICOM_TAG_SOP_CLASS_UID) + { + ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded); + } + + if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme, + DicomReplaceMode mode) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + InvalidateCache(); + + DcmDataset& dicom = *pimpl_->file_->getDataset(); + if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) + { + // Either the tag was previously existing (and now removed), or + // the replace mode was set to "InsertIfAbsent" + + if (decodeDataUriScheme && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(utf8Value)) + { + return; + } + } + + std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); + FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding()); + + InsertInternal(dicom, element.release()); + UpdateStorageUid(tag, utf8Value, false); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme, + DicomReplaceMode mode) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + InvalidateCache(); + + DcmDataset& dicom = *pimpl_->file_->getDataset(); + if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) + { + // Either the tag was previously existing (and now removed), or + // the replace mode was set to "InsertIfAbsent" + + if (decodeDataUriScheme && + value.type() == Json::stringValue && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(value.asString())) + { + return; + } + } + + InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); + + if (tag == DICOM_TAG_SOP_CLASS_UID || + tag == DICOM_TAG_SOP_INSTANCE_UID) + { + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + UpdateStorageUid(tag, value.asString(), decodeDataUriScheme); + } + } + } + + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 + void ParsedDicomFile::Answer(RestApiOutput& output) + { + std::string serialized; + if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset())) + { + output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); + } + } +#endif + + + bool ParsedDicomFile::GetTagValue(std::string& value, + const DicomTag& tag) + { + DcmTagKey k(tag.GetGroup(), tag.GetElement()); + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + if (tag.IsPrivate() || + FromDcmtkBridge::IsUnknownTag(tag) || + tag == DICOM_TAG_PIXEL_DATA || + tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) + { + const Uint8* data = NULL; // This is freed in the destructor of the dataset + long unsigned int count = 0; + + if (dataset.findAndGetUint8Array(k, data, &count).good()) + { + if (count > 0) + { + assert(data != NULL); + value.assign(reinterpret_cast<const char*>(data), count); + } + else + { + value.clear(); + } + + return true; + } + else + { + return false; + } + } + else + { + DcmElement* element = NULL; + if (!dataset.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + std::set<DicomTag> tmp; + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_Default, + 0, GetEncoding(), tmp)); + + if (v.get() == NULL || + v->IsNull()) + { + value = ""; + } + else + { + // TODO v->IsBinary() + value = v->GetContent(); + } + + return true; + } + } + + + DicomInstanceHasher ParsedDicomFile::GetHasher() + { + std::string patientId, studyUid, seriesUid, instanceUid; + + if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) || + !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || + !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || + !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); + } + + + void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer) + { + FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset()); + } + + +#if ORTHANC_SANDBOXED == 0 + void ParsedDicomFile::SaveToFile(const std::string& path) + { + // TODO Avoid using a temporary memory buffer, write directly on disk + std::string content; + SaveToMemoryBuffer(content); + SystemToolbox::WriteFile(content, path); + } +#endif + + + ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat); + + if (createIdentifiers) + { + ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); + ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); + ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); + ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } + } + + + void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source, + Encoding defaultEncoding) + { + pimpl_->file_.reset(new DcmFileFormat); + + const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET); + + if (tmp == NULL) + { + SetEncoding(defaultEncoding); + } + else if (tmp->IsBinary()) + { + LOG(ERROR) << "Invalid binary string in the SpecificCharacterSet (0008,0005) tag"; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else if (tmp->IsNull() || + tmp->GetContent().empty()) + { + SetEncoding(defaultEncoding); + } + else + { + Encoding encoding; + + if (GetDicomEncoding(encoding, tmp->GetContent().c_str())) + { + SetEncoding(encoding); + } + else + { + LOG(ERROR) << "Unsupported value for the SpecificCharacterSet (0008,0005) tag: \"" + << tmp->GetContent() << "\""; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + for (DicomMap::Map::const_iterator + it = source.map_.begin(); it != source.map_.end(); ++it) + { + if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET && + !it->second->IsNull()) + { + ReplacePlainString(it->first, it->second->GetContent()); + } + } + } + + + ParsedDicomFile::ParsedDicomFile(const DicomMap& map, + Encoding defaultEncoding) : + pimpl_(new PImpl) + { + CreateFromDicomMap(map, defaultEncoding); + } + + + ParsedDicomFile::ParsedDicomFile(const DicomMap& map) : + pimpl_(new PImpl) + { + CreateFromDicomMap(map, GetDefaultDicomEncoding()); + } + + + ParsedDicomFile::ParsedDicomFile(const void* content, + size_t size) : pimpl_(new PImpl) + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size)); + } + + ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl) + { + if (content.size() == 0) + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0)); + } + else + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size())); + } + } + + + ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other, + bool keepSopInstanceUid) : + pimpl_(new PImpl) + { + pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone())); + + if (!keepSopInstanceUid) + { + // Create a new instance-level identifier + ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } + } + + + ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat(&dicom)); + } + + + ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat(dicom)); + } + + + DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const + { + return *pimpl_->file_.get(); + } + + + ParsedDicomFile* ParsedDicomFile::Clone(bool keepSopInstanceUid) + { + return new ParsedDicomFile(*this, keepSopInstanceUid); + } + + + bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme) + { + std::string mime, content; + if (!Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme)) + { + return false; + } + + Toolbox::ToLowerCase(mime); + + if (mime == "image/png") + { +#if ORTHANC_ENABLE_PNG == 1 + EmbedImage(mime, content); +#else + LOG(ERROR) << "Orthanc was compiled without support of PNG"; + throw OrthancException(ErrorCode_NotImplemented); +#endif + } + else if (mime == "image/jpeg") + { +#if ORTHANC_ENABLE_JPEG == 1 + EmbedImage(mime, content); +#else + LOG(ERROR) << "Orthanc was compiled without support of JPEG"; + throw OrthancException(ErrorCode_NotImplemented); +#endif + } + else if (mime == "application/pdf") + { + EmbedPdf(content); + } + else + { + LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime; + throw OrthancException(ErrorCode_NotImplemented); + } + + return true; + } + + + void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme) + { + if (!EmbedContentInternal(dataUriScheme)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + +#if (ORTHANC_ENABLE_JPEG == 1 && \ + ORTHANC_ENABLE_PNG == 1) + void ParsedDicomFile::EmbedImage(const std::string& mime, + const std::string& content) + { + if (mime == "image/png") + { + PngReader reader; + reader.ReadFromMemory(content); + EmbedImage(reader); + } + else if (mime == "image/jpeg") + { + JpegReader reader; + reader.ReadFromMemory(content); + EmbedImage(reader); + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } +#endif + + + void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor) + { + if (accessor.GetFormat() != PixelFormat_Grayscale8 && + accessor.GetFormat() != PixelFormat_Grayscale16 && + accessor.GetFormat() != PixelFormat_SignedGrayscale16 && + accessor.GetFormat() != PixelFormat_RGB24 && + accessor.GetFormat() != PixelFormat_RGBA32) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + InvalidateCache(); + + if (accessor.GetFormat() == PixelFormat_RGBA32) + { + LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM"; + } + + // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html + + Remove(DICOM_TAG_PIXEL_DATA); + ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth())); + ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight())); + ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); + ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1"); + + if (accessor.GetFormat() == PixelFormat_SignedGrayscale16) + { + ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1"); + } + else + { + ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels + } + + ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved + ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); + + unsigned int bytesPerPixel = 0; + + switch (accessor.GetFormat()) + { + case PixelFormat_Grayscale8: + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); + bytesPerPixel = 1; + break; + + case PixelFormat_RGB24: + case PixelFormat_RGBA32: + ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB"); + ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3"); + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); + bytesPerPixel = 3; + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "16"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "15"); + bytesPerPixel = 2; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + assert(bytesPerPixel != 0); + + DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key)); + + unsigned int pitch = accessor.GetWidth() * bytesPerPixel; + Uint8* target = NULL; + pixels->createUint8Array(accessor.GetHeight() * pitch, target); + + for (unsigned int y = 0; y < accessor.GetHeight(); y++) + { + switch (accessor.GetFormat()) + { + case PixelFormat_RGB24: + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch); + target += pitch; + break; + } + + case PixelFormat_RGBA32: + { + // The alpha channel is not supported by the DICOM standard + const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)); + for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4) + { + target[0] = source[0]; + target[1] = source[1]; + target[2] = source[2]; + } + + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + Encoding ParsedDicomFile::GetEncoding() const + { + return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset(), + GetDefaultDicomEncoding()); + } + + + void ParsedDicomFile::SetEncoding(Encoding encoding) + { + if (encoding == Encoding_Windows1251) + { + // This Cyrillic codepage is not officially supported by the + // DICOM standard. Do not set the SpecificCharacterSet tag. + return; + } + + std::string s = GetDicomSpecificCharacterSet(encoding); + ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s); + } + + void ParsedDicomFile::DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), + format, flags, maxStringLength, + GetDefaultDicomEncoding(), ignoreTagLength); + } + + + void ParsedDicomFile::DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + const std::set<DicomTag>& ignoreTagLength) + { + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), + format, flags, maxStringLength, + GetDefaultDicomEncoding(), ignoreTagLength); + } + + + void ParsedDicomFile::DatasetToJson(Json::Value& target, + const std::set<DicomTag>& ignoreTagLength) + { + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), ignoreTagLength); + } + + + void ParsedDicomFile::DatasetToJson(Json::Value& target) + { + const std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), ignoreTagLength); + } + + + void ParsedDicomFile::HeaderToJson(Json::Value& target, + DicomToJsonFormat format) + { + FromDcmtkBridge::ExtractHeaderAsJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0); + } + + + bool ParsedDicomFile::HasTag(const DicomTag& tag) const + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + return pimpl_->file_->getDataset()->tagExists(key); + } + + + void ParsedDicomFile::EmbedPdf(const std::string& pdf) + { + if (pdf.size() < 5 || // (*) + strncmp("%PDF-", pdf.c_str(), 5) != 0) + { + LOG(ERROR) << "Not a PDF file"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + InvalidateCache(); + + ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT"); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD"); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf"); + //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); + + std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); + + size_t s = pdf.size(); + if (s & 1) + { + // The size of the buffer must be even + s += 1; + } + + Uint8* bytes = NULL; + OFCondition result = element->createUint8Array(s, bytes); + if (!result.good() || bytes == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) ) + bytes[s - 1] = 0; + + memcpy(bytes, pdf.c_str(), pdf.size()); + + DcmPolymorphOBOW* obj = element.release(); + result = pimpl_->file_->getDataset()->insert(obj); + + if (!result.good()) + { + delete obj; + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + + bool ParsedDicomFile::ExtractPdf(std::string& pdf) + { + std::string sop, mime; + + if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) || + !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) || + sop != UID_EncapsulatedPDFStorage || + mime != "application/pdf") + { + return false; + } + + if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT)) + { + return false; + } + + // Strip the possible pad byte at the end of file, because the + // encapsulated documents must always have an even length. The PDF + // format expects files to end with %%EOF followed by CR/LF. If + // the last character of the file is not a CR or LF, we assume it + // is a pad byte and remove it. + if (pdf.size() > 0) + { + char last = *pdf.rbegin(); + + if (last != 10 && last != 13) + { + pdf.resize(pdf.size() - 1); + } + } + + return true; + } + + + ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json, + DicomFromJsonFlags flags) + { + const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false; + const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false; + + std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers)); + result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding())); + + const Json::Value::Members tags = json.getMemberNames(); + + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + const Json::Value& value = json[tags[i]]; + + if (tag == DICOM_TAG_PIXEL_DATA || + tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) + { + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + else + { + result->EmbedContent(value.asString()); + } + } + else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent); + } + } + + return result.release(); + } + + + void ParsedDicomFile::GetRawFrame(std::string& target, + std::string& mime, + unsigned int frameId) + { + if (pimpl_->frameIndex_.get() == NULL) + { + pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_)); + } + + pimpl_->frameIndex_->GetRawFrame(target, frameId); + + E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); + switch (transferSyntax) + { + case EXS_JPEGProcess1: + mime = "image/jpeg"; + break; + + case EXS_JPEG2000LosslessOnly: + case EXS_JPEG2000: + mime = "image/jp2"; + break; + + default: + mime = "application/octet-stream"; + break; + } + } + + + void ParsedDicomFile::InvalidateCache() + { + pimpl_->frameIndex_.reset(NULL); + } + + + unsigned int ParsedDicomFile::GetFramesCount() const + { + return DicomFrameIndex::GetFramesCount(*pimpl_->file_); + } + + + void ParsedDicomFile::ChangeEncoding(Encoding target) + { + Encoding source = GetEncoding(); + + if (source != target) // Avoid unnecessary conversion + { + ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target)); + FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, target); + } + } + + + void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const + { + FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset()); + } + + + bool ParsedDicomFile::LookupTransferSyntax(std::string& result) + { + return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_); + } + + + bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const + { + DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(), + DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement()); + + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + const char *c = NULL; + if (dataset.findAndGetString(k, c).good() && + c != NULL) + { + result = StringToPhotometricInterpretation(c); + return true; + } + else + { + return false; + } + } + + + void ParsedDicomFile::Apply(ITagVisitor& visitor) + { + FromDcmtkBridge::Apply(*pimpl_->file_->getDataset(), visitor, GetDefaultDicomEncoding()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ParsedDicomFile.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,234 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_ENABLE_JPEG) +# error Macro ORTHANC_ENABLE_JPEG must be defined to use this file +#endif + +#if !defined(ORTHANC_ENABLE_PNG) +# error Macro ORTHANC_ENABLE_PNG must be defined to use this file +#endif + +#if !defined(ORTHANC_ENABLE_CIVETWEB) +# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to use this file +#endif + +#if !defined(ORTHANC_ENABLE_MONGOOSE) +# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to use this file +#endif + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#include "ITagVisitor.h" +#include "../DicomFormat/DicomInstanceHasher.h" +#include "../Images/ImageAccessor.h" +#include "../IDynamicObject.h" +#include "../Toolbox.h" + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 +# include "../RestApi/RestApiOutput.h" +#endif + +#include <boost/shared_ptr.hpp> + + +class DcmDataset; +class DcmFileFormat; + +namespace Orthanc +{ + class ParsedDicomFile : public IDynamicObject + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + ParsedDicomFile(ParsedDicomFile& other, + bool keepSopInstanceUid); + + void CreateFromDicomMap(const DicomMap& source, + Encoding defaultEncoding); + + void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep); + + void UpdateStorageUid(const DicomTag& tag, + const std::string& value, + bool decodeDataUriScheme); + + void InvalidateCache(); + + bool EmbedContentInternal(const std::string& dataUriScheme); + + public: + ParsedDicomFile(bool createIdentifiers); // Create a minimal DICOM instance + + ParsedDicomFile(const DicomMap& map, + Encoding defaultEncoding); + + ParsedDicomFile(const DicomMap& map); + + ParsedDicomFile(const void* content, + size_t size); + + ParsedDicomFile(const std::string& content); + + ParsedDicomFile(DcmDataset& dicom); + + ParsedDicomFile(DcmFileFormat& dicom); + + DcmFileFormat& GetDcmtkObject() const; + + ParsedDicomFile* Clone(bool keepSopInstanceUid); + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 + void SendPathValue(RestApiOutput& output, + const UriComponents& uri); + + void Answer(RestApiOutput& output); +#endif + + void Remove(const DicomTag& tag); + + // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) + void Clear(const DicomTag& tag, + bool onlyIfExists); + + void Replace(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme, + DicomReplaceMode mode); + + void Replace(const DicomTag& tag, + const Json::Value& value, // Assumed to be encoded with UTF-8 + bool decodeDataUriScheme, + DicomReplaceMode mode); + + void Insert(const DicomTag& tag, + const Json::Value& value, // Assumed to be encoded with UTF-8 + bool decodeDataUriScheme); + + void ReplacePlainString(const DicomTag& tag, + const std::string& utf8Value) + { + Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent); + } + + void RemovePrivateTags() + { + RemovePrivateTagsInternal(NULL); + } + + void RemovePrivateTags(const std::set<DicomTag>& toKeep) + { + RemovePrivateTagsInternal(&toKeep); + } + + // WARNING: This function handles the decoding of strings to UTF8 + bool GetTagValue(std::string& value, + const DicomTag& tag); + + DicomInstanceHasher GetHasher(); + + void SaveToMemoryBuffer(std::string& buffer); + +#if ORTHANC_SANDBOXED == 0 + void SaveToFile(const std::string& path); +#endif + + void EmbedContent(const std::string& dataUriScheme); + + void EmbedImage(const ImageAccessor& accessor); + +#if (ORTHANC_ENABLE_JPEG == 1 && \ + ORTHANC_ENABLE_PNG == 1) + void EmbedImage(const std::string& mime, + const std::string& content); +#endif + + Encoding GetEncoding() const; + + // WARNING: This function only sets the encoding, it will not + // convert the encoding of the tags. Use "ChangeEncoding()" if need be. + void SetEncoding(Encoding encoding); + + void DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength); + + void DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + const std::set<DicomTag>& ignoreTagLength); + + // This version uses the default parameters for + // FileContentType_DicomAsJson + void DatasetToJson(Json::Value& target, + const std::set<DicomTag>& ignoreTagLength); + + void DatasetToJson(Json::Value& target); + + void HeaderToJson(Json::Value& target, + DicomToJsonFormat format); + + bool HasTag(const DicomTag& tag) const; + + void EmbedPdf(const std::string& pdf); + + bool ExtractPdf(std::string& pdf); + + void GetRawFrame(std::string& target, // OUT + std::string& mime, // OUT + unsigned int frameId); // IN + + unsigned int GetFramesCount() const; + + static ParsedDicomFile* CreateFromJson(const Json::Value& value, + DicomFromJsonFlags flags); + + void ChangeEncoding(Encoding target); + + void ExtractDicomSummary(DicomMap& target) const; + + bool LookupTransferSyntax(std::string& result); + + bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const; + + void Apply(ITagVisitor& visitor); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ToDcmtkBridge.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,149 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ToDcmtkBridge.h" + +#include <memory> + +#include "../OrthancException.h" + + +namespace Orthanc +{ + DcmEVR ToDcmtkBridge::Convert(ValueRepresentation vr) + { + switch (vr) + { + case ValueRepresentation_ApplicationEntity: + return EVR_AE; + + case ValueRepresentation_AgeString: + return EVR_AS; + + case ValueRepresentation_AttributeTag: + return EVR_AT; + + case ValueRepresentation_CodeString: + return EVR_CS; + + case ValueRepresentation_Date: + return EVR_DA; + + case ValueRepresentation_DecimalString: + return EVR_DS; + + case ValueRepresentation_DateTime: + return EVR_DT; + + case ValueRepresentation_FloatingPointSingle: + return EVR_FL; + + case ValueRepresentation_FloatingPointDouble: + return EVR_FD; + + case ValueRepresentation_IntegerString: + return EVR_IS; + + case ValueRepresentation_LongString: + return EVR_LO; + + case ValueRepresentation_LongText: + return EVR_LT; + + case ValueRepresentation_OtherByte: + return EVR_OB; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_OtherDouble: + return EVR_OD;*/ + + case ValueRepresentation_OtherFloat: + return EVR_OF; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_OtherLong: + return EVR_OL;*/ + + case ValueRepresentation_OtherWord: + return EVR_OW; + + case ValueRepresentation_PersonName: + return EVR_PN; + + case ValueRepresentation_ShortString: + return EVR_SH; + + case ValueRepresentation_SignedLong: + return EVR_SL; + + case ValueRepresentation_Sequence: + return EVR_SQ; + + case ValueRepresentation_SignedShort: + return EVR_SS; + + case ValueRepresentation_ShortText: + return EVR_ST; + + case ValueRepresentation_Time: + return EVR_TM; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_UnlimitedCharacters: + return EVR_UC;*/ + + case ValueRepresentation_UniqueIdentifier: + return EVR_UI; + + case ValueRepresentation_UnsignedLong: + return EVR_UL; + + case ValueRepresentation_Unknown: + return EVR_UN; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_UniversalResource: + return EVR_UR;*/ + + case ValueRepresentation_UnsignedShort: + return EVR_US; + + case ValueRepresentation_UnlimitedText: + return EVR_UT; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ToDcmtkBridge.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +#endif + +#include "../DicomFormat/DicomMap.h" +#include <dcmtk/dcmdata/dcdatset.h> + +namespace Orthanc +{ + class ToDcmtkBridge + { + public: + static DcmTagKey Convert(const DicomTag& tag) + { + return DcmTagKey(tag.GetGroup(), tag.GetElement()); + } + + static DcmEVR Convert(ValueRepresentation vr); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Endianness.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,220 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 + + +/******************************************************************** + ** LINUX-LIKE ARCHITECTURES + ********************************************************************/ + +#if defined(__LSB_VERSION__) +// Linux Standard Base (LSB) does not come with be16toh, be32toh, and +// be64toh +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0 +# include <endian.h> +#elif defined(__linux__) || defined(__EMSCRIPTEN__) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# include <endian.h> +#endif + + +/******************************************************************** + ** WINDOWS ARCHITECTURES + ** + ** On Windows x86, "host" will always be little-endian ("le"). + ********************************************************************/ + +#if defined(_WIN32) +# if defined(_MSC_VER) +// Visual Studio - http://msdn.microsoft.com/en-us/library/a3140177.aspx +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# define be16toh(x) _byteswap_ushort(x) +# define be32toh(x) _byteswap_ulong(x) +# define be64toh(x) _byteswap_uint64(x) +# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +// MinGW >= 4.3 - Use builtin intrinsic for byte swapping +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# define be16toh(x) __builtin_bswap16(x) +# define be32toh(x) __builtin_bswap32(x) +# define be64toh(x) __builtin_bswap64(x) +# else +// MinGW <= 4.2, we must manually implement the byte swapping (*) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0 +# define be16toh(x) __orthanc_bswap16(x) +# define be32toh(x) __orthanc_bswap32(x) +# define be64toh(x) __orthanc_bswap64(x) +# endif + +# define htobe16(x) be16toh(x) +# define htobe32(x) be32toh(x) +# define htobe64(x) be64toh(x) + +# define htole16(x) x +# define htole32(x) x +# define htole64(x) x + +# define le16toh(x) x +# define le32toh(x) x +# define le64toh(x) x +#endif + + +/******************************************************************** + ** FREEBSD ARCHITECTURES + ********************************************************************/ + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# include <arpa/inet.h> +#endif + + +/******************************************************************** + ** OPENBSD ARCHITECTURES + ********************************************************************/ + +#if defined(__OpenBSD__) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# include <endian.h> +#endif + + +/******************************************************************** + ** APPLE ARCHITECTURES (including OS X) + ********************************************************************/ + +#if defined(__APPLE__) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# include <libkern/OSByteOrder.h> +# define be16toh(x) OSSwapBigToHostInt16(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define be64toh(x) OSSwapBigToHostInt64(x) + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htobe64(x) OSSwapHostToBigInt64(x) + +# define htole16(x) OSSwapHostToLittleInt16(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define htole64(x) OSSwapHostToLittleInt64(x) + +# define le16toh(x) OSSwapLittleToHostInt16(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) +#endif + + +/******************************************************************** + ** PORTABLE (BUT SLOW) IMPLEMENTATION OF BYTE-SWAPPING + ********************************************************************/ + +#if ORTHANC_HAS_BUILTIN_BYTE_SWAP != 1 + +#include <stdint.h> + +static inline uint16_t __orthanc_bswap16(uint16_t a) +{ + /** + * Note that an alternative implementation was included in Orthanc + * 1.4.0 and 1.4.1: + * + * # hg log -p -r 2706 + * + * This alternative implementation only hid an underlying problem + * with pointer alignment on some architectures, and was thus + * reverted. Check out issue #99: + * https://bitbucket.org/sjodogne/orthanc/issues/99 + **/ + return (a << 8) | (a >> 8); +} + +static inline uint32_t __orthanc_bswap32(uint32_t a) +{ + const uint8_t* p = reinterpret_cast<const uint8_t*>(&a); + return (static_cast<uint32_t>(p[0]) << 24 | + static_cast<uint32_t>(p[1]) << 16 | + static_cast<uint32_t>(p[2]) << 8 | + static_cast<uint32_t>(p[3])); +} + +static inline uint64_t __orthanc_bswap64(uint64_t a) +{ + const uint8_t* p = reinterpret_cast<const uint8_t*>(&a); + return (static_cast<uint64_t>(p[0]) << 56 | + static_cast<uint64_t>(p[1]) << 48 | + static_cast<uint64_t>(p[2]) << 40 | + static_cast<uint64_t>(p[3]) << 32 | + static_cast<uint64_t>(p[4]) << 24 | + static_cast<uint64_t>(p[5]) << 16 | + static_cast<uint64_t>(p[6]) << 8 | + static_cast<uint64_t>(p[7])); +} + +#if defined(_WIN32) +// Implemented above (*) +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN) +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define be16toh(x) __orthanc_bswap16(x) +# define be32toh(x) __orthanc_bswap32(x) +# define be64toh(x) __orthanc_bswap64(x) +# define htobe16(x) __orthanc_bswap16(x) +# define htobe32(x) __orthanc_bswap32(x) +# define htobe64(x) __orthanc_bswap64(x) +# define htole16(x) x +# define htole32(x) x +# define htole64(x) x +# define le16toh(x) x +# define le32toh(x) x +# define le64toh(x) x +# elif __BYTE_ORDER == __BIG_ENDIAN +# define be16toh(x) x +# define be32toh(x) x +# define be64toh(x) x +# define htobe16(x) x +# define htobe32(x) x +# define htobe64(x) x +# define htole16(x) __orthanc_bswap16(x) +# define htole32(x) __orthanc_bswap32(x) +# define htole64(x) __orthanc_bswap64(x) +# define le16toh(x) __orthanc_bswap16(x) +# define le32toh(x) __orthanc_bswap32(x) +# define le64toh(x) __orthanc_bswap64(x) +# else +# error Please support your platform here +# endif +#else +# error Please support your platform here +#endif + +#endif
--- a/Core/EnumerationDictionary.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/EnumerationDictionary.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,6 +61,11 @@ stringToEnumeration_.clear(); } + bool Contains(Enumeration value) const + { + return enumerationToString_.find(value) != enumerationToString_.end(); + } + void Add(Enumeration value, const std::string& str) { // Check if these values are free @@ -80,10 +86,9 @@ { try { - int value = boost::lexical_cast<int>(str); - return static_cast<Enumeration>(value); + return static_cast<Enumeration>(boost::lexical_cast<int>(str)); } - catch (boost::bad_lexical_cast) + catch (boost::bad_lexical_cast&) { }
--- a/Core/Enumerations.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Enumerations.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,8 +36,11 @@ #include "OrthancException.h" #include "Toolbox.h" +#include "Logging.h" +#include <boost/thread/mutex.hpp> #include <string.h> +#include <cassert> namespace Orthanc { @@ -62,7 +66,7 @@ return "Parameter out of range"; case ErrorCode_NotEnoughMemory: - return "Not enough memory"; + return "The server hosting Orthanc is running out of memory"; case ErrorCode_BadParameterType: return "Bad type for a parameter"; @@ -151,6 +155,18 @@ case ErrorCode_EmptyRequest: return "The request is empty"; + case ErrorCode_NotAcceptable: + return "Cannot send a response which is acceptable according to the Accept HTTP header"; + + case ErrorCode_NullPointer: + return "Cannot handle a NULL pointer"; + + case ErrorCode_DatabaseUnavailable: + return "The database is currently not available (probably a transient situation)"; + + case ErrorCode_CanceledJob: + return "This job was canceled"; + case ErrorCode_SQLiteNotOpened: return "SQLite: The database is not opened"; @@ -209,10 +225,10 @@ return "The specified path does not point to a directory"; case ErrorCode_HttpPortInUse: - return "The TCP port of the HTTP server is already in use"; + return "The TCP port of the HTTP server is privileged or already in use"; case ErrorCode_DicomPortInUse: - return "The TCP port of the DICOM server is already in use"; + return "The TCP port of the DICOM server is privileged or already in use"; case ErrorCode_BadHttpStatusInRest: return "This HTTP status is not allowed in a REST API"; @@ -322,6 +338,12 @@ case ErrorCode_CannotOrderSlices: return "Unable to order the slices of the series"; + case ErrorCode_NoWorklistHandler: + return "No request handler factory for DICOM C-Find Modality SCP"; + + case ErrorCode_AlreadyExistingTag: + return "Cannot override the value of a tag that already exists"; + default: if (error >= ErrorCode_START_PLUGINS) { @@ -621,10 +643,10 @@ return "RGB"; case PhotometricInterpretation_Monochrome1: - return "Monochrome1"; + return "MONOCHROME1"; case PhotometricInterpretation_Monochrome2: - return "Monochrome2"; + return "MONOCHROME2"; case PhotometricInterpretation_ARGB: return "ARGB"; @@ -636,25 +658,25 @@ return "HSV"; case PhotometricInterpretation_Palette: - return "Palette color"; + return "PALETTE COLOR"; case PhotometricInterpretation_YBRFull: - return "YBR full"; + return "YBR_FULL"; case PhotometricInterpretation_YBRFull422: - return "YBR full 422"; + return "YBR_FULL_422"; case PhotometricInterpretation_YBRPartial420: - return "YBR partial 420"; + return "YBR_PARTIAL_420"; case PhotometricInterpretation_YBRPartial422: - return "YBR partial 422"; + return "YBR_PARTIAL_422"; case PhotometricInterpretation_YBR_ICT: - return "YBR ICT"; + return "YBR_ICT"; case PhotometricInterpretation_YBR_RCT: - return "YBR RCT"; + return "YBR_RCT"; case PhotometricInterpretation_Unknown: return "Unknown"; @@ -675,8 +697,8 @@ case RequestOrigin_DicomProtocol: return "DicomProtocol"; - case RequestOrigin_Http: - return "Http"; + case RequestOrigin_RestApi: + return "RestApi"; case RequestOrigin_Plugins: return "Plugins"; @@ -712,6 +734,290 @@ } + const char* EnumerationToString(PixelFormat format) + { + switch (format) + { + case PixelFormat_RGB24: + return "RGB24"; + + case PixelFormat_RGBA32: + return "RGBA32"; + + case PixelFormat_BGRA32: + return "BGRA32"; + + case PixelFormat_Grayscale8: + return "Grayscale (unsigned 8bpp)"; + + case PixelFormat_Grayscale16: + return "Grayscale (unsigned 16bpp)"; + + case PixelFormat_SignedGrayscale16: + return "Grayscale (signed 16bpp)"; + + case PixelFormat_Float32: + return "Grayscale (float 32bpp)"; + + case PixelFormat_Grayscale32: + return "Grayscale (unsigned 32bpp)"; + + case PixelFormat_Grayscale64: + return "Grayscale (unsigned 64bpp)"; + + case PixelFormat_RGB48: + return "RGB48"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(ModalityManufacturer manufacturer) + { + switch (manufacturer) + { + case ModalityManufacturer_Generic: + return "Generic"; + + case ModalityManufacturer_GenericNoWildcardInDates: + return "GenericNoWildcardInDates"; + + case ModalityManufacturer_GenericNoUniversalWildcard: + return "GenericNoUniversalWildcard"; + + case ModalityManufacturer_StoreScp: + return "StoreScp"; + + case ModalityManufacturer_ClearCanvas: + return "ClearCanvas"; + + case ModalityManufacturer_Dcm4Chee: + return "Dcm4Chee"; + + case ModalityManufacturer_Vitrea: + return "Vitrea"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(DicomRequestType type) + { + switch (type) + { + case DicomRequestType_Echo: + return "Echo"; + break; + + case DicomRequestType_Find: + return "Find"; + break; + + case DicomRequestType_Get: + return "Get"; + break; + + case DicomRequestType_Move: + return "Move"; + break; + + case DicomRequestType_Store: + return "Store"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(TransferSyntax syntax) + { + switch (syntax) + { + case TransferSyntax_Deflated: + return "Deflated"; + + case TransferSyntax_Jpeg: + return "JPEG"; + + case TransferSyntax_Jpeg2000: + return "JPEG2000"; + + case TransferSyntax_JpegLossless: + return "JPEG Lossless"; + + case TransferSyntax_Jpip: + return "JPIP"; + + case TransferSyntax_Mpeg2: + return "MPEG2"; + + case TransferSyntax_Rle: + return "RLE"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(DicomVersion version) + { + switch (version) + { + case DicomVersion_2008: + return "2008"; + break; + + case DicomVersion_2017c: + return "2017c"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(ValueRepresentation vr) + { + switch (vr) + { + case ValueRepresentation_ApplicationEntity: // AE + return "AE"; + + case ValueRepresentation_AgeString: // AS + return "AS"; + + case ValueRepresentation_AttributeTag: // AT (2 x uint16_t) + return "AT"; + + case ValueRepresentation_CodeString: // CS + return "CS"; + + case ValueRepresentation_Date: // DA + return "DA"; + + case ValueRepresentation_DecimalString: // DS + return "DS"; + + case ValueRepresentation_DateTime: // DT + return "DT"; + + case ValueRepresentation_FloatingPointSingle: // FL (float) + return "FL"; + + case ValueRepresentation_FloatingPointDouble: // FD (double) + return "FD"; + + case ValueRepresentation_IntegerString: // IS + return "IS"; + + case ValueRepresentation_LongString: // LO + return "LO"; + + case ValueRepresentation_LongText: // LT + return "LT"; + + case ValueRepresentation_OtherByte: // OB + return "OB"; + + case ValueRepresentation_OtherDouble: // OD + return "OD"; + + case ValueRepresentation_OtherFloat: // OF + return "OF"; + + case ValueRepresentation_OtherLong: // OL + return "OL"; + + case ValueRepresentation_OtherWord: // OW + return "OW"; + + case ValueRepresentation_PersonName: // PN + return "PN"; + + case ValueRepresentation_ShortString: // SH + return "SH"; + + case ValueRepresentation_SignedLong: // SL (int32_t) + return "SL"; + + case ValueRepresentation_Sequence: // SQ + return "SQ"; + + case ValueRepresentation_SignedShort: // SS (int16_t) + return "SS"; + + case ValueRepresentation_ShortText: // ST + return "ST"; + + case ValueRepresentation_Time: // TM + return "TM"; + + case ValueRepresentation_UnlimitedCharacters: // UC + return "UC"; + + case ValueRepresentation_UniqueIdentifier: // UI (UID) + return "UI"; + + case ValueRepresentation_UnsignedLong: // UL (uint32_t) + return "UL"; + + case ValueRepresentation_Unknown: // UN + return "UN"; + + case ValueRepresentation_UniversalResource: // UR (URI or URL) + return "UR"; + + case ValueRepresentation_UnsignedShort: // US (uint16_t) + return "US"; + + case ValueRepresentation_UnlimitedText: // UT + return "UT"; + + case ValueRepresentation_NotSupported: + return "Not supported"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(JobState state) + { + switch (state) + { + case JobState_Pending: + return "Pending"; + + case JobState_Running: + return "Running"; + + case JobState_Success: + return "Success"; + + case JobState_Failure: + return "Failure"; + + case JobState_Paused: + return "Paused"; + + case JobState_Retry: + return "Retry"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); @@ -862,6 +1168,368 @@ } + ValueRepresentation StringToValueRepresentation(const std::string& vr, + bool throwIfUnsupported) + { + if (vr == "AE") + { + return ValueRepresentation_ApplicationEntity; + } + else if (vr == "AS") + { + return ValueRepresentation_AgeString; + } + else if (vr == "AT") + { + return ValueRepresentation_AttributeTag; + } + else if (vr == "CS") + { + return ValueRepresentation_CodeString; + } + else if (vr == "DA") + { + return ValueRepresentation_Date; + } + else if (vr == "DS") + { + return ValueRepresentation_DecimalString; + } + else if (vr == "DT") + { + return ValueRepresentation_DateTime; + } + else if (vr == "FL") + { + return ValueRepresentation_FloatingPointSingle; + } + else if (vr == "FD") + { + return ValueRepresentation_FloatingPointDouble; + } + else if (vr == "IS") + { + return ValueRepresentation_IntegerString; + } + else if (vr == "LO") + { + return ValueRepresentation_LongString; + } + else if (vr == "LT") + { + return ValueRepresentation_LongText; + } + else if (vr == "OB") + { + return ValueRepresentation_OtherByte; + } + else if (vr == "OD") + { + return ValueRepresentation_OtherDouble; + } + else if (vr == "OF") + { + return ValueRepresentation_OtherFloat; + } + else if (vr == "OL") + { + return ValueRepresentation_OtherLong; + } + else if (vr == "OW") + { + return ValueRepresentation_OtherWord; + } + else if (vr == "PN") + { + return ValueRepresentation_PersonName; + } + else if (vr == "SH") + { + return ValueRepresentation_ShortString; + } + else if (vr == "SL") + { + return ValueRepresentation_SignedLong; + } + else if (vr == "SQ") + { + return ValueRepresentation_Sequence; + } + else if (vr == "SS") + { + return ValueRepresentation_SignedShort; + } + else if (vr == "ST") + { + return ValueRepresentation_ShortText; + } + else if (vr == "TM") + { + return ValueRepresentation_Time; + } + else if (vr == "UC") + { + return ValueRepresentation_UnlimitedCharacters; + } + else if (vr == "UI") + { + return ValueRepresentation_UniqueIdentifier; + } + else if (vr == "UL") + { + return ValueRepresentation_UnsignedLong; + } + else if (vr == "UN") + { + return ValueRepresentation_Unknown; + } + else if (vr == "UR") + { + return ValueRepresentation_UniversalResource; + } + else if (vr == "US") + { + return ValueRepresentation_UnsignedShort; + } + else if (vr == "UT") + { + return ValueRepresentation_UnlimitedText; + } + else + { + std::string s = "Unsupported value representation encountered: " + vr; + + if (throwIfUnsupported) + { + LOG(ERROR) << s; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + LOG(INFO) << s; + return ValueRepresentation_NotSupported; + } + } + } + + + PhotometricInterpretation StringToPhotometricInterpretation(const char* value) + { + // http://dicom.nema.org/medical/dicom/2017a/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2 + std::string s(value); + + if (s == "MONOCHROME1") + { + return PhotometricInterpretation_Monochrome1; + } + + if (s == "MONOCHROME2") + { + return PhotometricInterpretation_Monochrome2; + } + + if (s == "PALETTE COLOR") + { + return PhotometricInterpretation_Palette; + } + + if (s == "RGB") + { + return PhotometricInterpretation_RGB; + } + + if (s == "HSV") + { + return PhotometricInterpretation_HSV; + } + + if (s == "ARGB") + { + return PhotometricInterpretation_ARGB; + } + + if (s == "CMYK") + { + return PhotometricInterpretation_CMYK; + } + + if (s == "YBR_FULL") + { + return PhotometricInterpretation_YBRFull; + } + + if (s == "YBR_FULL_422") + { + return PhotometricInterpretation_YBRFull422; + } + + if (s == "YBR_PARTIAL_422") + { + return PhotometricInterpretation_YBRPartial422; + } + + if (s == "YBR_PARTIAL_420") + { + return PhotometricInterpretation_YBRPartial420; + } + + if (s == "YBR_ICT") + { + return PhotometricInterpretation_YBR_ICT; + } + + if (s == "YBR_RCT") + { + return PhotometricInterpretation_YBR_RCT; + } + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer) + { + ModalityManufacturer result; + bool obsolete = false; + + if (manufacturer == "Generic") + { + return ModalityManufacturer_Generic; + } + else if (manufacturer == "GenericNoWildcardInDates") + { + return ModalityManufacturer_GenericNoWildcardInDates; + } + else if (manufacturer == "GenericNoUniversalWildcard") + { + 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; + } + else if (manufacturer == "AgfaImpax" || + manufacturer == "SyngoVia") + { + result = ModalityManufacturer_GenericNoWildcardInDates; + obsolete = true; + } + else if (manufacturer == "EFilm2" || + manufacturer == "MedInria") + { + result = ModalityManufacturer_Generic; + obsolete = true; + } + else + { + LOG(ERROR) << "Unknown modality manufacturer: \"" << manufacturer << "\""; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (obsolete) + { + LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since " + << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc " + << "releases, you should replace it by \"" + << EnumerationToString(result) + << "\" in your configuration file."; + } + + return result; + } + + + DicomVersion StringToDicomVersion(const std::string& version) + { + if (version == "2008") + { + return DicomVersion_2008; + } + else if (version == "2017c") + { + return DicomVersion_2017c; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + JobState StringToJobState(const std::string& state) + { + if (state == "Pending") + { + return JobState_Pending; + } + else if (state == "Running") + { + return JobState_Running; + } + else if (state == "Success") + { + return JobState_Success; + } + else if (state == "Failure") + { + return JobState_Failure; + } + else if (state == "Paused") + { + return JobState_Paused; + } + else if (state == "Retry") + { + return JobState_Retry; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + RequestOrigin StringToRequestOrigin(const std::string& origin) + { + if (origin == "Unknown") + { + return RequestOrigin_Unknown; + } + else if (origin == "DicomProtocol") + { + return RequestOrigin_DicomProtocol; + } + else if (origin == "RestApi") + { + return RequestOrigin_RestApi; + } + else if (origin == "Plugins") + { + return RequestOrigin_Plugins; + } + else if (origin == "Lua") + { + return RequestOrigin_Lua; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format) @@ -877,8 +1545,20 @@ return 3; case PixelFormat_RGBA32: + case PixelFormat_BGRA32: + case PixelFormat_Grayscale32: return 4; + case PixelFormat_Float32: + assert(sizeof(float) == 4); + return 4; + + case PixelFormat_RGB48: + return 6; + + case PixelFormat_Grayscale64: + return 8; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -888,15 +1568,18 @@ bool GetDicomEncoding(Encoding& encoding, const char* specificCharacterSet) { - std::string s = specificCharacterSet; + std::string s = Toolbox::StripSpaces(specificCharacterSet); Toolbox::ToUpperCase(s); - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2 // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java if (s == "ISO_IR 6" || - s == "ISO_IR 192" || s == "ISO 2022 IR 6") { + encoding = Encoding_Ascii; + } + else if (s == "ISO_IR 192") + { encoding = Encoding_Utf8; } else if (s == "ISO_IR 100" || @@ -952,8 +1635,15 @@ { encoding = Encoding_Japanese; } - else if (s == "GB18030") + else if (s == "GB18030" || s == "GBK") { + /** + * According to tumashu@163.com, "In China, many dicom file's + * 0008,0005 tag is set as "GBK", instead of "GB18030", GBK is a + * subset of GB18030, and which is used frequently in China, + * suggest support it." + * https://groups.google.com/d/msg/orthanc-users/WMM8LMbjpUc/02-1f_yFCgAJ + **/ encoding = Encoding_Chinese; } /* @@ -980,39 +1670,6 @@ } - const char* GetMimeType(FileContentType type) - { - switch (type) - { - case FileContentType_Dicom: - return "application/dicom"; - - case FileContentType_DicomAsJson: - return "application/json"; - - default: - return "application/octet-stream"; - } - } - - - const char* GetFileExtension(FileContentType type) - { - switch (type) - { - case FileContentType_Dicom: - return ".dcm"; - - case FileContentType_DicomAsJson: - return ".json"; - - default: - // Unknown file type - return ""; - } - } - - ResourceType GetChildResourceType(ResourceType type) { switch (type) @@ -1073,11 +1730,13 @@ const char* GetDicomSpecificCharacterSet(Encoding encoding) { - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2 switch (encoding) { + case Encoding_Ascii: + return "ISO_IR 6"; + case Encoding_Utf8: - case Encoding_Ascii: return "ISO_IR 192"; case Encoding_Latin1: @@ -1164,8 +1823,100 @@ case ErrorCode_Unauthorized: return HttpStatus_401_Unauthorized; + case ErrorCode_NotAcceptable: + return HttpStatus_406_NotAcceptable; + + case ErrorCode_DatabaseUnavailable: + return HttpStatus_503_ServiceUnavailable; + default: return HttpStatus_500_InternalServerError; } } + + + bool IsUserContentType(FileContentType type) + { + return (type >= FileContentType_StartUser && + type <= FileContentType_EndUser); + } + + + bool IsBinaryValueRepresentation(ValueRepresentation vr) + { + // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html + + switch (vr) + { + case ValueRepresentation_ApplicationEntity: // AE + case ValueRepresentation_AgeString: // AS + case ValueRepresentation_CodeString: // CS + case ValueRepresentation_Date: // DA + case ValueRepresentation_DecimalString: // DS + case ValueRepresentation_DateTime: // DT + case ValueRepresentation_IntegerString: // IS + case ValueRepresentation_LongString: // LO + case ValueRepresentation_LongText: // LT + case ValueRepresentation_PersonName: // PN + case ValueRepresentation_ShortString: // SH + case ValueRepresentation_ShortText: // ST + case ValueRepresentation_Time: // TM + case ValueRepresentation_UnlimitedCharacters: // UC + case ValueRepresentation_UniqueIdentifier: // UI (UID) + case ValueRepresentation_UniversalResource: // UR (URI or URL) + case ValueRepresentation_UnlimitedText: // UT + { + return false; + } + + /** + * Below are all the VR whose character repertoire is tagged as + * "not applicable" + **/ + case ValueRepresentation_AttributeTag: // AT (2 x uint16_t) + case ValueRepresentation_FloatingPointSingle: // FL (float) + case ValueRepresentation_FloatingPointDouble: // FD (double) + case ValueRepresentation_OtherByte: // OB + case ValueRepresentation_OtherDouble: // OD + case ValueRepresentation_OtherFloat: // OF + case ValueRepresentation_OtherLong: // OL + case ValueRepresentation_OtherWord: // OW + case ValueRepresentation_SignedLong: // SL (int32_t) + case ValueRepresentation_Sequence: // SQ + case ValueRepresentation_SignedShort: // SS (int16_t) + case ValueRepresentation_UnsignedLong: // UL (uint32_t) + case ValueRepresentation_Unknown: // UN + case ValueRepresentation_UnsignedShort: // US (uint16_t) + { + return true; + } + + case ValueRepresentation_NotSupported: + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static boost::mutex defaultEncodingMutex_; // Should not be necessary + static Encoding defaultEncoding_ = ORTHANC_DEFAULT_DICOM_ENCODING; + + Encoding GetDefaultDicomEncoding() + { + boost::mutex::scoped_lock lock(defaultEncodingMutex_); + return defaultEncoding_; + } + + void SetDefaultDicomEncoding(Encoding encoding) + { + std::string name = EnumerationToString(encoding); + + { + boost::mutex::scoped_lock lock(defaultEncodingMutex_); + defaultEncoding_ = encoding; + } + + LOG(INFO) << "Default encoding for DICOM was changed to: " << name; + } + }
--- a/Core/Enumerations.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Enumerations.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +33,18 @@ #pragma once +#include <string> + + +#if defined(_MSC_VER) +# define ORTHANC_FORCE_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) || defined(__EMSCRIPTEN__) +# define ORTHANC_FORCE_INLINE inline __attribute((always_inline)) +#else +# error Please support your compiler here +#endif + + namespace Orthanc { enum Endianness @@ -50,7 +63,7 @@ ErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, ErrorCode_NotImplemented = 2 /*!< Not implemented yet */, ErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, - ErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + ErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */, ErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, ErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, ErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, @@ -80,6 +93,10 @@ ErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, ErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, ErrorCode_EmptyRequest = 33 /*!< The request is empty */, + ErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + ErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, + ErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + ErrorCode_CanceledJob = 37 /*!< This job was canceled */, ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, @@ -99,8 +116,8 @@ ErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, ErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, ErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, - ErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, - ErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, + ErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is privileged or already in use */, + ErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is privileged or already in use */, ErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, ErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, ErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, @@ -137,6 +154,8 @@ ErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, ErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, 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_START_PLUGINS = 1000000 }; @@ -184,7 +203,35 @@ * {summary}{Graylevel, signed 16bpp image.} * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.} **/ - PixelFormat_SignedGrayscale16 = 5 + PixelFormat_SignedGrayscale16 = 5, + + /** + * {summary}{Graylevel, floating-point image.} + * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.} + **/ + PixelFormat_Float32 = 6, + + // This is the memory layout for Cairo (for internal use in Stone of Orthanc) + PixelFormat_BGRA32 = 7, + + /** + * {summary}{Graylevel, unsigned 32bpp image.} + * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.} + **/ + PixelFormat_Grayscale32 = 8, + + /** + * {summary}{Color image in RGB48 format.} + * {description}{This format describes a color image. The pixels are stored in 6 + * consecutive bytes. The memory layout is RGB.} + **/ + PixelFormat_RGB48 = 9, + + /** + * {summary}{Graylevel, unsigned 64bpp image.} + * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.} + **/ + PixelFormat_Grayscale64 = 10 }; @@ -312,7 +359,8 @@ }; - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ + // Specific Character Sets + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2 enum Encoding { Encoding_Ascii, @@ -336,7 +384,7 @@ }; - // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.2/ + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.2 enum PhotometricInterpretation { PhotometricInterpretation_ARGB, // Retired @@ -368,11 +416,169 @@ { RequestOrigin_Unknown, RequestOrigin_DicomProtocol, - RequestOrigin_Http, + RequestOrigin_RestApi, RequestOrigin_Plugins, RequestOrigin_Lua }; + enum ServerBarrierEvent + { + ServerBarrierEvent_Stop, + ServerBarrierEvent_Reload // SIGHUP signal: reload configuration file + }; + + enum FileMode + { + FileMode_ReadBinary, + FileMode_WriteBinary + }; + + /** + * The value representations Orthanc knows about. They correspond to + * the DICOM 2016b version of the standard. + * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html + **/ + enum ValueRepresentation + { + ValueRepresentation_ApplicationEntity = 1, // AE + ValueRepresentation_AgeString = 2, // AS + ValueRepresentation_AttributeTag = 3, // AT (2 x uint16_t) + ValueRepresentation_CodeString = 4, // CS + ValueRepresentation_Date = 5, // DA + ValueRepresentation_DecimalString = 6, // DS + ValueRepresentation_DateTime = 7, // DT + ValueRepresentation_FloatingPointSingle = 8, // FL (float) + ValueRepresentation_FloatingPointDouble = 9, // FD (double) + ValueRepresentation_IntegerString = 10, // IS + ValueRepresentation_LongString = 11, // LO + ValueRepresentation_LongText = 12, // LT + ValueRepresentation_OtherByte = 13, // OB + ValueRepresentation_OtherDouble = 14, // OD + ValueRepresentation_OtherFloat = 15, // OF + ValueRepresentation_OtherLong = 16, // OL + ValueRepresentation_OtherWord = 17, // OW + ValueRepresentation_PersonName = 18, // PN + ValueRepresentation_ShortString = 19, // SH + ValueRepresentation_SignedLong = 20, // SL (int32_t) + ValueRepresentation_Sequence = 21, // SQ + ValueRepresentation_SignedShort = 22, // SS (int16_t) + ValueRepresentation_ShortText = 23, // ST + ValueRepresentation_Time = 24, // TM + ValueRepresentation_UnlimitedCharacters = 25, // UC + ValueRepresentation_UniqueIdentifier = 26, // UI (UID) + ValueRepresentation_UnsignedLong = 27, // UL (uint32_t) + ValueRepresentation_Unknown = 28, // UN + ValueRepresentation_UniversalResource = 29, // UR (URI or URL) + ValueRepresentation_UnsignedShort = 30, // US (uint16_t) + ValueRepresentation_UnlimitedText = 31, // UT + ValueRepresentation_NotSupported // Not supported by Orthanc, or tag not in dictionary + }; + + enum DicomReplaceMode + { + DicomReplaceMode_InsertIfAbsent, + DicomReplaceMode_ThrowIfAbsent, + DicomReplaceMode_IgnoreIfAbsent + }; + + enum DicomToJsonFormat + { + DicomToJsonFormat_Full, + DicomToJsonFormat_Short, + DicomToJsonFormat_Human + }; + + enum DicomToJsonFlags + { + DicomToJsonFlags_IncludeBinary = (1 << 0), + DicomToJsonFlags_IncludePrivateTags = (1 << 1), + DicomToJsonFlags_IncludeUnknownTags = (1 << 2), + DicomToJsonFlags_IncludePixelData = (1 << 3), + DicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), + DicomToJsonFlags_ConvertBinaryToNull = (1 << 5), + + // Some predefined combinations + DicomToJsonFlags_None = 0, + DicomToJsonFlags_Default = (DicomToJsonFlags_IncludeBinary | + DicomToJsonFlags_IncludePixelData | + DicomToJsonFlags_IncludePrivateTags | + DicomToJsonFlags_IncludeUnknownTags | + DicomToJsonFlags_ConvertBinaryToNull) + }; + + enum DicomFromJsonFlags + { + DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0), + DicomFromJsonFlags_GenerateIdentifiers = (1 << 1), + + // Some predefined combinations + DicomFromJsonFlags_None = 0 + }; + + enum DicomVersion + { + DicomVersion_2008, + DicomVersion_2017c + }; + + enum ModalityManufacturer + { + ModalityManufacturer_Generic, + ModalityManufacturer_GenericNoWildcardInDates, + ModalityManufacturer_GenericNoUniversalWildcard, + ModalityManufacturer_StoreScp, + ModalityManufacturer_ClearCanvas, + ModalityManufacturer_Dcm4Chee, + ModalityManufacturer_Vitrea + }; + + enum DicomRequestType + { + DicomRequestType_Echo, + DicomRequestType_Find, + DicomRequestType_Get, + DicomRequestType_Move, + DicomRequestType_Store + }; + + enum TransferSyntax + { + TransferSyntax_Deflated, + TransferSyntax_Jpeg, + TransferSyntax_Jpeg2000, + TransferSyntax_JpegLossless, + TransferSyntax_Jpip, + TransferSyntax_Mpeg2, + TransferSyntax_Rle + }; + + enum JobState + { + JobState_Pending, + JobState_Running, + JobState_Success, + JobState_Failure, + JobState_Paused, + JobState_Retry + }; + + enum JobStepCode + { + JobStepCode_Success, + JobStepCode_Failure, + JobStepCode_Continue, + JobStepCode_Retry + }; + + enum JobStopReason + { + JobStopReason_Paused, + JobStopReason_Canceled, + JobStopReason_Success, + JobStopReason_Failure, + JobStopReason_Retry + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -441,23 +647,46 @@ const char* EnumerationToString(RequestOrigin origin); + const char* EnumerationToString(PixelFormat format); + + const char* EnumerationToString(ModalityManufacturer manufacturer); + + const char* EnumerationToString(DicomRequestType type); + + const char* EnumerationToString(TransferSyntax syntax); + + const char* EnumerationToString(DicomVersion version); + + const char* EnumerationToString(ValueRepresentation vr); + + const char* EnumerationToString(JobState state); + Encoding StringToEncoding(const char* encoding); ResourceType StringToResourceType(const char* type); ImageFormat StringToImageFormat(const char* format); - LogLevel StringToLogLevel(const char* format); + LogLevel StringToLogLevel(const char* level); + + ValueRepresentation StringToValueRepresentation(const std::string& vr, + bool throwIfUnsupported); + + PhotometricInterpretation StringToPhotometricInterpretation(const char* value); + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); + + DicomVersion StringToDicomVersion(const std::string& version); + + JobState StringToJobState(const std::string& state); + + RequestOrigin StringToRequestOrigin(const std::string& origin); + unsigned int GetBytesPerPixel(PixelFormat format); bool GetDicomEncoding(Encoding& encoding, const char* specificCharacterSet); - const char* GetMimeType(FileContentType type); - - const char* GetFileExtension(FileContentType type); - ResourceType GetChildResourceType(ResourceType type); ResourceType GetParentResourceType(ResourceType type); @@ -467,4 +696,12 @@ const char* GetDicomSpecificCharacterSet(Encoding encoding); HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error); + + bool IsUserContentType(FileContentType type); + + bool IsBinaryValueRepresentation(ValueRepresentation vr); + + Encoding GetDefaultDicomEncoding(); + + void SetDefaultDicomEncoding(Encoding encoding); }
--- a/Core/FileStorage/FileInfo.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/FileStorage/FileInfo.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/FileStorage/FilesystemStorage.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +40,7 @@ #include "../Logging.h" #include "../OrthancException.h" #include "../Toolbox.h" -#include "../Uuid.h" +#include "../SystemToolbox.h" #include <boost/filesystem/fstream.hpp> @@ -83,14 +84,40 @@ //root_ = boost::filesystem::absolute(root).string(); root_ = root; - Toolbox::MakeDirectory(root); + SystemToolbox::MakeDirectory(root); } + + + static const char* GetDescriptionInternal(FileContentType content) + { + // This function is for logging only (internal use), a more + // fully-featured version is available in ServerEnumerations.cpp + switch (content) + { + case FileContentType_Unknown: + return "Unknown"; + + case FileContentType_Dicom: + return "DICOM"; + + case FileContentType_DicomAsJson: + return "JSON summary of DICOM"; + + default: + return "User-defined"; + } + } + + void FilesystemStorage::Create(const std::string& uuid, const void* content, size_t size, - FileContentType /*type*/) + FileContentType type) { + LOG(INFO) << "Creating attachment \"" << uuid << "\" of \"" << GetDescriptionInternal(type) + << "\" type (size: " << (size / (1024 * 1024) + 1) << "MB)"; + boost::filesystem::path path; path = GetPath(uuid); @@ -117,33 +144,19 @@ } } - boost::filesystem::ofstream f; - f.open(path, std::ofstream::out | std::ios::binary); - if (!f.good()) - { - throw OrthancException(ErrorCode_FileStorageCannotWrite); - } - - if (size != 0) - { - f.write(static_cast<const char*>(content), size); - if (!f.good()) - { - f.close(); - throw OrthancException(ErrorCode_FileStorageCannotWrite); - } - } - - f.close(); + SystemToolbox::WriteFile(content, size, path.string()); } void FilesystemStorage::Read(std::string& content, const std::string& uuid, - FileContentType /*type*/) + FileContentType type) { + LOG(INFO) << "Reading attachment \"" << uuid << "\" of \"" << GetDescriptionInternal(type) + << "\" content type"; + content.clear(); - Toolbox::ReadFile(content, GetPath(uuid).string()); + SystemToolbox::ReadFile(content, GetPath(uuid).string()); } @@ -165,7 +178,7 @@ { for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current) { - if (fs::is_regular_file(current->status())) + if (SystemToolbox::IsRegularFile(current->path().string())) { try { @@ -186,7 +199,7 @@ } } } - catch (fs::filesystem_error) + catch (fs::filesystem_error&) { } } @@ -211,11 +224,9 @@ void FilesystemStorage::Remove(const std::string& uuid, - FileContentType /*type*/) + FileContentType type) { -#if ORTHANC_ENABLE_GOOGLE_LOG == 1 - LOG(INFO) << "Deleting file " << uuid; -#endif + LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast<int>(type); namespace fs = boost::filesystem;
--- a/Core/FileStorage/FilesystemStorage.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/FileStorage/FilesystemStorage.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +33,14 @@ #pragma once +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The class FilesystemStorage cannot be used in sandboxed environments +#endif + #include "IStorageArea.h" #include <stdint.h> @@ -52,7 +61,7 @@ boost::filesystem::path GetPath(const std::string& uuid) const; public: - FilesystemStorage(std::string root); + explicit FilesystemStorage(std::string root); virtual void Create(const std::string& uuid, const void* content,
--- a/Core/FileStorage/IStorageArea.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/FileStorage/IStorageArea.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/FileStorage/MemoryStorageArea.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,128 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "MemoryStorageArea.h" + +#include "../OrthancException.h" +#include "../Logging.h" + +namespace Orthanc +{ + MemoryStorageArea::~MemoryStorageArea() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + if (it->second != NULL) + { + delete it->second; + } + } + } + + void MemoryStorageArea::Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type) + { + LOG(INFO) << "Creating attachment \"" << uuid << "\" of \"" << static_cast<int>(type) + << "\" type (size: " << (size / (1024 * 1024) + 1) << "MB)"; + + boost::mutex::scoped_lock lock(mutex_); + + if (size != 0 && + content == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else if (content_.find(uuid) != content_.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + content_[uuid] = new std::string(reinterpret_cast<const char*>(content), size); + } + } + + + void MemoryStorageArea::Read(std::string& content, + const std::string& uuid, + FileContentType type) + { + LOG(INFO) << "Reading attachment \"" << uuid << "\" of \"" + << static_cast<int>(type) << "\" content type"; + + boost::mutex::scoped_lock lock(mutex_); + + Content::const_iterator found = content_.find(uuid); + + if (found == content_.end()) + { + throw OrthancException(ErrorCode_InexistentFile); + } + else if (found->second == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + content.assign(*found->second); + } + } + + + void MemoryStorageArea::Remove(const std::string& uuid, + FileContentType type) + { + LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast<int>(type); + + boost::mutex::scoped_lock lock(mutex_); + + Content::iterator found = content_.find(uuid); + + if (found == content_.end()) + { + // Ignore second removal + } + else if (found->second == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + delete found->second; + content_.erase(found); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/FileStorage/MemoryStorageArea.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IStorageArea.h" + +#include <boost/thread/mutex.hpp> +#include <map> + +namespace Orthanc +{ + class MemoryStorageArea : public IStorageArea + { + private: + typedef std::map<std::string, std::string*> Content; + + boost::mutex mutex_; + Content content_; + + public: + virtual ~MemoryStorageArea(); + + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type); + + virtual void Read(std::string& content, + const std::string& uuid, + FileContentType type); + + virtual void Remove(const std::string& uuid, + FileContentType type); + }; +}
--- a/Core/FileStorage/StorageAccessor.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/FileStorage/StorageAccessor.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +36,12 @@ #include "../Compression/ZlibCompressor.h" #include "../OrthancException.h" -#include "../HttpServer/HttpStreamTranscoder.h" +#include "../Toolbox.h" +#include "../Toolbox.h" + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 +# include "../HttpServer/HttpStreamTranscoder.h" +#endif namespace Orthanc { @@ -140,33 +146,59 @@ } +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void StorageAccessor::SetupSender(BufferHttpSender& sender, - const FileInfo& info) + const FileInfo& info, + const std::string& mime) { area_.Read(sender.GetBuffer(), info.GetUuid(), info.GetContentType()); - sender.SetContentType(GetMimeType(info.GetContentType())); - sender.SetContentFilename(info.GetUuid() + std::string(GetFileExtension(info.GetContentType()))); + sender.SetContentType(mime); + + const char* extension; + switch (info.GetContentType()) + { + case FileContentType_Dicom: + extension = ".dcm"; + break; + + case FileContentType_DicomAsJson: + extension = ".json"; + break; + + default: + // Non-standard content type + extension = ""; + } + + sender.SetContentFilename(info.GetUuid() + std::string(extension)); } +#endif +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void StorageAccessor::AnswerFile(HttpOutput& output, - const FileInfo& info) + const FileInfo& info, + const std::string& mime) { BufferHttpSender sender; - SetupSender(sender, info); + SetupSender(sender, info, mime); HttpStreamTranscoder transcoder(sender, info.GetCompressionType()); output.Answer(transcoder); } +#endif +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void StorageAccessor::AnswerFile(RestApiOutput& output, - const FileInfo& info) + const FileInfo& info, + const std::string& mime) { BufferHttpSender sender; - SetupSender(sender, info); + SetupSender(sender, info, mime); HttpStreamTranscoder transcoder(sender, info.GetCompressionType()); output.AnswerStream(transcoder); } +#endif }
--- a/Core/FileStorage/StorageAccessor.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/FileStorage/StorageAccessor.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,10 +33,29 @@ #pragma once +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The class StorageAccessor cannot be used in sandboxed environments +#endif + +#if !defined(ORTHANC_ENABLE_CIVETWEB) +# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to use this file +#endif + +#if !defined(ORTHANC_ENABLE_MONGOOSE) +# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to use this file +#endif + #include "IStorageArea.h" #include "FileInfo.h" -#include "../HttpServer/BufferHttpSender.h" -#include "../RestApi/RestApiOutput.h" + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 +# include "../HttpServer/BufferHttpSender.h" +# include "../RestApi/RestApiOutput.h" +#endif #include <vector> #include <string> @@ -50,8 +70,11 @@ private: IStorageArea& area_; +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void SetupSender(BufferHttpSender& sender, - const FileInfo& info); + const FileInfo& info, + const std::string& mime); +#endif public: StorageAccessor(IStorageArea& area) : area_(area) @@ -84,10 +107,14 @@ area_.Remove(info.GetUuid(), info.GetContentType()); } +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void AnswerFile(HttpOutput& output, - const FileInfo& info); + const FileInfo& info, + const std::string& mime); void AnswerFile(RestApiOutput& output, - const FileInfo& info); + const FileInfo& info, + const std::string& mime); +#endif }; }
--- a/Core/HttpClient.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpClient.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +37,19 @@ #include "Toolbox.h" #include "OrthancException.h" #include "Logging.h" +#include "ChunkedBuffer.h" +#include "SystemToolbox.h" #include <string.h> #include <curl/curl.h> #include <boost/algorithm/string/predicate.hpp> +#include <boost/thread/mutex.hpp> -static std::string globalCACertificates_; -static bool globalVerifyPeers_ = true; -static long globalTimeout_ = 0; +#if ORTHANC_ENABLE_PKCS11 == 1 +# include "Pkcs11.h" +#endif + extern "C" { @@ -69,7 +74,12 @@ #endif static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status) { +#if ORTHANC_ENABLE_SSL == 1 return GetHttpStatus(curl_easy_perform(curl), curl, status); +#else + LOG(ERROR) << "Orthanc was compiled without SSL support, cannot make HTTPS request"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); +#endif } } @@ -77,14 +87,116 @@ namespace Orthanc { + class HttpClient::GlobalParameters + { + private: + boost::mutex mutex_; + bool httpsVerifyPeers_; + std::string httpsCACertificates_; + std::string proxy_; + long timeout_; + bool verbose_; + + GlobalParameters() : + httpsVerifyPeers_(true), + timeout_(0), + verbose_(false) + { + } + + public: + // Singleton pattern + static GlobalParameters& GetInstance() + { + static GlobalParameters parameters; + return parameters; + } + + void ConfigureSsl(bool httpsVerifyPeers, + const std::string& httpsCACertificates) + { + boost::mutex::scoped_lock lock(mutex_); + httpsVerifyPeers_ = httpsVerifyPeers; + httpsCACertificates_ = httpsCACertificates; + } + + void GetSslConfiguration(bool& httpsVerifyPeers, + std::string& httpsCACertificates) + { + boost::mutex::scoped_lock lock(mutex_); + httpsVerifyPeers = httpsVerifyPeers_; + httpsCACertificates = httpsCACertificates_; + } + + void SetDefaultProxy(const std::string& proxy) + { + LOG(INFO) << "Setting the default proxy for HTTP client connections: " << proxy; + + { + boost::mutex::scoped_lock lock(mutex_); + proxy_ = proxy; + } + } + + void GetDefaultProxy(std::string& target) + { + boost::mutex::scoped_lock lock(mutex_); + target = proxy_; + } + + void SetDefaultTimeout(long seconds) + { + LOG(INFO) << "Setting the default timeout for HTTP client connections: " << seconds << " seconds"; + + { + boost::mutex::scoped_lock lock(mutex_); + timeout_ = seconds; + } + } + + long GetDefaultTimeout() + { + boost::mutex::scoped_lock lock(mutex_); + return timeout_; + } + +#if ORTHANC_ENABLE_PKCS11 == 1 + bool IsPkcs11Initialized() + { + boost::mutex::scoped_lock lock(mutex_); + return Pkcs11::IsInitialized(); + } + + void InitializePkcs11(const std::string& module, + const std::string& pin, + bool verbose) + { + boost::mutex::scoped_lock lock(mutex_); + Pkcs11::Initialize(module, pin, verbose); + } +#endif + + bool IsDefaultVerbose() const + { + return verbose_; + } + + void SetDefaultVerbose(bool verbose) + { + verbose_ = verbose; + } + }; + + struct HttpClient::PImpl { CURL* curl_; - struct curl_slist *postHeaders_; + struct curl_slist *defaultPostHeaders_; + struct curl_slist *userHeaders_; }; - static void ThrowException(HttpStatus status) + void HttpClient::ThrowException(HttpStatus status) { switch (status) { @@ -92,10 +204,11 @@ throw OrthancException(ErrorCode_BadRequest); case HttpStatus_401_Unauthorized: + case HttpStatus_403_Forbidden: throw OrthancException(ErrorCode_Unauthorized); case HttpStatus_404_NotFound: - throw OrthancException(ErrorCode_InexistentItem); + throw OrthancException(ErrorCode_UnknownResource); default: throw OrthancException(ErrorCode_NetworkProtocol); @@ -103,9 +216,15 @@ } - static CURLcode CheckCode(CURLcode code) { + if (code == CURLE_NOT_BUILT_IN) + { + LOG(ERROR) << "Your libcurl does not contain a required feature, " + << "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF"; + throw OrthancException(ErrorCode_InternalError); + } + if (code != CURLE_OK) { LOG(ERROR) << "libCURL error: " + std::string(curl_easy_strerror(code)); @@ -116,27 +235,104 @@ } - static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *payload) + static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload) { - std::string& target = *(static_cast<std::string*>(payload)); + ChunkedBuffer& target = *(static_cast<ChunkedBuffer*>(payload)); size_t length = size * nmemb; if (length == 0) + { return 0; + } + else + { + target.AddChunk(buffer, length); + return length; + } + } + - size_t pos = target.size(); + /*static int CurlDebugCallback(CURL *handle, + curl_infotype type, + char *data, + size_t size, + void *userptr) + { + switch (type) + { + case CURLINFO_TEXT: + case CURLINFO_HEADER_IN: + case CURLINFO_HEADER_OUT: + case CURLINFO_SSL_DATA_IN: + case CURLINFO_SSL_DATA_OUT: + case CURLINFO_END: + case CURLINFO_DATA_IN: + case CURLINFO_DATA_OUT: + { + std::string s(data, size); + LOG(INFO) << "libcurl: " << s; + break; + } + + default: + break; + } + + return 0; + }*/ + - target.resize(pos + length); - memcpy(&target.at(pos), buffer, length); + struct CurlHeaderParameters + { + bool lowerCase_; + HttpClient::HttpHeaders* headers_; + }; + + + static size_t CurlHeaderCallback(void *buffer, size_t size, size_t nmemb, void *payload) + { + CurlHeaderParameters& parameters = *(static_cast<CurlHeaderParameters*>(payload)); + assert(parameters.headers_ != NULL); - return length; + size_t length = size * nmemb; + if (length == 0) + { + return 0; + } + else + { + std::string s(reinterpret_cast<const char*>(buffer), length); + std::size_t colon = s.find(':'); + std::size_t eol = s.find("\r\n"); + if (colon != std::string::npos && + eol != std::string::npos) + { + std::string tmp(s.substr(0, colon)); + + if (parameters.lowerCase_) + { + Toolbox::ToLowerCase(tmp); + } + + std::string key = Toolbox::StripSpaces(tmp); + + if (!key.empty()) + { + std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol)); + (*parameters.headers_) [key] = value; + } + } + + return length; + } } void HttpClient::Setup() { - pimpl_->postHeaders_ = NULL; - if ((pimpl_->postHeaders_ = curl_slist_append(pimpl_->postHeaders_, "Expect:")) == NULL) + pimpl_->userHeaders_ = NULL; + pimpl_->defaultPostHeaders_ = NULL; + if ((pimpl_->defaultPostHeaders_ = curl_slist_append(pimpl_->defaultPostHeaders_, "Expect:")) == NULL) { throw OrthancException(ErrorCode_NotEnoughMemory); } @@ -144,11 +340,11 @@ pimpl_->curl_ = curl_easy_init(); if (!pimpl_->curl_) { - curl_slist_free_all(pimpl_->postHeaders_); + curl_slist_free_all(pimpl_->defaultPostHeaders_); throw OrthancException(ErrorCode_NotEnoughMemory); } - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlCallback)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); @@ -160,30 +356,56 @@ url_ = ""; method_ = HttpMethod_Get; lastStatus_ = HttpStatus_200_Ok; - isVerbose_ = false; - timeout_ = globalTimeout_; - verifyPeers_ = globalVerifyPeers_; + SetVerbose(GlobalParameters::GetInstance().IsDefaultVerbose()); + timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout(); + GlobalParameters::GetInstance().GetDefaultProxy(proxy_); + GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_); } - HttpClient::HttpClient() : pimpl_(new PImpl) + HttpClient::HttpClient() : + pimpl_(new PImpl), + verifyPeers_(true), + pkcs11Enabled_(false), + headersToLowerCase_(true), + redirectionFollowed_(true) { Setup(); } - HttpClient::HttpClient(const HttpClient& other) : pimpl_(new PImpl) + HttpClient::HttpClient(const WebServiceParameters& service, + const std::string& uri) : + pimpl_(new PImpl), + verifyPeers_(true), + headersToLowerCase_(true), + redirectionFollowed_(true) { Setup(); - if (other.IsVerbose()) + if (service.GetUsername().size() != 0 && + service.GetPassword().size() != 0) { - SetVerbose(true); + SetCredentials(service.GetUsername().c_str(), + service.GetPassword().c_str()); } - if (other.credentials_.size() != 0) + if (!service.GetCertificateFile().empty()) { - credentials_ = other.credentials_; + SetClientCertificate(service.GetCertificateFile(), + service.GetCertificateKeyFile(), + service.GetCertificateKeyPassword()); + } + + SetPkcs11Enabled(service.IsPkcs11Enabled()); + + SetUrl(service.GetUrl() + uri); + + for (WebServiceParameters::Dictionary::const_iterator + it = service.GetHttpHeaders().begin(); + it != service.GetHttpHeaders().end(); ++it) + { + AddHeader(it->first, it->second); } } @@ -191,7 +413,8 @@ HttpClient::~HttpClient() { curl_easy_cleanup(pimpl_->curl_); - curl_slist_free_all(pimpl_->postHeaders_); + curl_slist_free_all(pimpl_->defaultPostHeaders_); + ClearHeaders(); } @@ -202,6 +425,7 @@ if (isVerbose_) { CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1)); + //CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_DEBUGFUNCTION, &CurlDebugCallback)); } else { @@ -210,35 +434,140 @@ } - bool HttpClient::Apply(std::string& answer) + void HttpClient::AddHeader(const std::string& key, + const std::string& value) { - answer.clear(); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); + if (key.empty()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::string s = key + ": " + value; + + if ((pimpl_->userHeaders_ = curl_slist_append(pimpl_->userHeaders_, s.c_str())) == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + + void HttpClient::ClearHeaders() + { + if (pimpl_->userHeaders_ != NULL) + { + curl_slist_free_all(pimpl_->userHeaders_); + pimpl_->userHeaders_ = NULL; + } + } + + bool HttpClient::ApplyInternal(std::string& answerBody, + HttpHeaders* answerHeaders) + { + answerBody.clear(); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); + + CurlHeaderParameters headerParameters; + + if (answerHeaders == NULL) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, NULL)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, NULL)); + } + else + { + headerParameters.lowerCase_ = headersToLowerCase_; + headerParameters.headers_ = answerHeaders; + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters)); + } + +#if ORTHANC_ENABLE_SSL == 1 // Setup HTTPS-related options -#if ORTHANC_SSL_ENABLED == 1 - if (IsHttpsVerifyPeers()) + + if (verifyPeers_) { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, GetHttpsCACertificates().c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2)); // libcurl default is strict verifyhost CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); } else { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); } #endif + // Setup the HTTPS client certificate + if (!clientCertificateFile_.empty() && + pkcs11Enabled_) + { + LOG(ERROR) << "Cannot enable both client certificates and PKCS#11 authentication"; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (pkcs11Enabled_) + { +#if ORTHANC_ENABLE_PKCS11 == 1 + if (GlobalParameters::GetInstance().IsPkcs11Initialized()) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG")); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG")); + } + else + { + LOG(ERROR) << "Cannot use PKCS#11 for a HTTPS request, because it has not been initialized"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } +#else + LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11"; + throw OrthancException(ErrorCode_InternalError); +#endif + } + else if (!clientCertificateFile_.empty()) + { +#if ORTHANC_ENABLE_SSL == 1 + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM")); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str())); + + if (!clientCertificateKeyPassword_.empty()) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str())); + } + + // NB: If no "clientKeyFile_" is provided, the key must be + // prepended to the certificate file + if (!clientCertificateKeyFile_.empty()) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM")); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str())); + } +#else + LOG(ERROR) << "This version of Orthanc is compiled without OpenSSL support, cannot use HTTPS client authentication"; + throw OrthancException(ErrorCode_InternalError); +#endif + } + // Reset the parameters from previous calls to Apply() - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0L)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL)); + if (redirectionFollowed_) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1L)); + } + else + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 0L)); + } + // Set timeouts if (timeout_ <= 0) { @@ -269,7 +598,12 @@ case HttpMethod_Post: CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); + + if (pimpl_->userHeaders_ == NULL) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_)); + } + break; case HttpMethod_Delete: @@ -284,7 +618,12 @@ // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */ - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->postHeaders_)); + + if (pimpl_->userHeaders_ == NULL) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_)); + } + break; default: @@ -312,6 +651,9 @@ CURLcode code; long status = 0; + ChunkedBuffer buffer; + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &buffer)); + if (boost::starts_with(url_, "https://")) { code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status); @@ -321,6 +663,14 @@ code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status); } + LOG(INFO) << "HTTP status code " << status << " after " + << EnumerationToString(method_) << " request on: " << url_; + + if (isVerbose_) + { + LOG(INFO) << "cURL status code: " << code; + } + CheckCode(code); if (status == 0) @@ -333,17 +683,31 @@ lastStatus_ = static_cast<HttpStatus>(status); } - return (status >= 200 && status < 300); + bool success = (status >= 200 && status < 300); + + if (success) + { + buffer.Flatten(answerBody); + } + else + { + answerBody.clear(); + LOG(INFO) << "Error in HTTP request, received HTTP status " << status + << " (" << EnumerationToString(lastStatus_) << ")"; + } + + return success; } - bool HttpClient::Apply(Json::Value& answer) + bool HttpClient::ApplyInternal(Json::Value& answerBody, + HttpClient::HttpHeaders* answerHeaders) { std::string s; - if (Apply(s)) + if (ApplyInternal(s, answerHeaders)) { Json::Reader reader; - return reader.parse(s, answer); + return reader.parse(s, answerBody); } else { @@ -358,75 +722,148 @@ credentials_ = std::string(username) + ":" + std::string(password); } - - const std::string& HttpClient::GetHttpsCACertificates() const + + void HttpClient::ConfigureSsl(bool httpsVerifyPeers, + const std::string& httpsVerifyCertificates) { - if (caCertificates_.empty()) - { - return globalCACertificates_; - } - else - { - return caCertificates_; - } - } - - - void HttpClient::GlobalInitialize(bool httpsVerifyPeers, - const std::string& httpsVerifyCertificates) - { - globalVerifyPeers_ = httpsVerifyPeers; - globalCACertificates_ = httpsVerifyCertificates; - -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 if (httpsVerifyPeers) { - if (globalCACertificates_.empty()) + if (httpsVerifyCertificates.empty()) { LOG(WARNING) << "No certificates are provided to validate peers, " << "set \"HttpsCACertificates\" if you need to do HTTPS requests"; } else { - LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << globalCACertificates_; + LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << httpsVerifyCertificates; } } else { - LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled!"; + LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled"; } #endif - CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT)); + GlobalParameters::GetInstance().ConfigureSsl(httpsVerifyPeers, httpsVerifyCertificates); } + void HttpClient::GlobalInitialize() + { +#if ORTHANC_ENABLE_SSL == 1 + CheckCode(curl_global_init(CURL_GLOBAL_ALL)); +#else + CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL)); +#endif + } + + void HttpClient::GlobalFinalize() { curl_global_cleanup(); + +#if ORTHANC_ENABLE_PKCS11 == 1 + Pkcs11::Finalize(); +#endif + } + + + void HttpClient::SetDefaultVerbose(bool verbose) + { + GlobalParameters::GetInstance().SetDefaultVerbose(verbose); + } + + + void HttpClient::SetDefaultProxy(const std::string& proxy) + { + GlobalParameters::GetInstance().SetDefaultProxy(proxy); + } + + + void HttpClient::SetDefaultTimeout(long timeout) + { + GlobalParameters::GetInstance().SetDefaultTimeout(timeout); + } + + + void HttpClient::ApplyAndThrowException(std::string& answerBody) + { + if (!Apply(answerBody)) + { + ThrowException(GetLastStatus()); + } } - void HttpClient::SetDefaultTimeout(long timeout) + void HttpClient::ApplyAndThrowException(Json::Value& answerBody) { - LOG(INFO) << "Setting the default timeout for HTTP client connections: " << timeout << " seconds"; - globalTimeout_ = timeout; + if (!Apply(answerBody)) + { + ThrowException(GetLastStatus()); + } } - void HttpClient::ApplyAndThrowException(std::string& answer) + void HttpClient::ApplyAndThrowException(std::string& answerBody, + HttpHeaders& answerHeaders) { - if (!Apply(answer)) + if (!Apply(answerBody, answerHeaders)) { ThrowException(GetLastStatus()); } } - void HttpClient::ApplyAndThrowException(Json::Value& answer) + + void HttpClient::ApplyAndThrowException(Json::Value& answerBody, + HttpHeaders& answerHeaders) { - if (!Apply(answer)) + if (!Apply(answerBody, answerHeaders)) { ThrowException(GetLastStatus()); } } + + + void HttpClient::SetClientCertificate(const std::string& certificateFile, + const std::string& certificateKeyFile, + const std::string& certificateKeyPassword) + { + if (certificateFile.empty()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (!SystemToolbox::IsRegularFile(certificateFile)) + { + LOG(ERROR) << "Cannot open certificate file: " << certificateFile; + throw OrthancException(ErrorCode_InexistentFile); + } + + if (!certificateKeyFile.empty() && + !SystemToolbox::IsRegularFile(certificateKeyFile)) + { + LOG(ERROR) << "Cannot open key file: " << certificateKeyFile; + throw OrthancException(ErrorCode_InexistentFile); + } + + clientCertificateFile_ = certificateFile; + clientCertificateKeyFile_ = certificateKeyFile; + clientCertificateKeyPassword_ = certificateKeyPassword; + } + + + void HttpClient::InitializePkcs11(const std::string& module, + const std::string& pin, + bool verbose) + { +#if ORTHANC_ENABLE_PKCS11 == 1 + LOG(INFO) << "Initializing PKCS#11 using " << module + << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)"); + GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose); +#else + LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11"; + throw OrthancException(ErrorCode_InternalError); +#endif + } }
--- a/Core/HttpClient.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpClient.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,16 +34,31 @@ #pragma once #include "Enumerations.h" +#include "WebServiceParameters.h" #include <string> #include <boost/shared_ptr.hpp> #include <json/json.h> +#if !defined(ORTHANC_ENABLE_SSL) +# error The macro ORTHANC_ENABLE_SSL must be defined +#endif + +#if !defined(ORTHANC_ENABLE_PKCS11) +# error The macro ORTHANC_ENABLE_PKCS11 must be defined +#endif + + namespace Orthanc { class HttpClient { + public: + typedef std::map<std::string, std::string> HttpHeaders; + private: + class GlobalParameters; + struct PImpl; boost::shared_ptr<PImpl> pimpl_; @@ -56,15 +72,29 @@ std::string proxy_; bool verifyPeers_; std::string caCertificates_; + std::string clientCertificateFile_; + std::string clientCertificateKeyFile_; + std::string clientCertificateKeyPassword_; + bool pkcs11Enabled_; + bool headersToLowerCase_; + bool redirectionFollowed_; void Setup(); - void operator= (const HttpClient&); // Forbidden + void operator= (const HttpClient&); // Assignment forbidden + HttpClient(const HttpClient& base); // Copy forbidden + + bool ApplyInternal(std::string& answerBody, + HttpHeaders* answerHeaders); + + bool ApplyInternal(Json::Value& answerBody, + HttpHeaders* answerHeaders); public: - HttpClient(const HttpClient& base); + HttpClient(); - HttpClient(); + HttpClient(const WebServiceParameters& service, + const std::string& uri); ~HttpClient(); @@ -125,9 +155,32 @@ return isVerbose_; } - bool Apply(std::string& answer); + void AddHeader(const std::string& key, + const std::string& value); + + void ClearHeaders(); + + bool Apply(std::string& answerBody) + { + return ApplyInternal(answerBody, NULL); + } - bool Apply(Json::Value& answer); + bool Apply(Json::Value& answerBody) + { + return ApplyInternal(answerBody, NULL); + } + + bool Apply(std::string& answerBody, + HttpHeaders& answerHeaders) + { + return ApplyInternal(answerBody, &answerHeaders); + } + + bool Apply(Json::Value& answerBody, + HttpHeaders& answerHeaders) + { + return ApplyInternal(answerBody, &answerHeaders); + } HttpStatus GetLastStatus() const { @@ -157,17 +210,87 @@ caCertificates_ = certificates; } - const std::string& GetHttpsCACertificates() const; + const std::string& GetHttpsCACertificates() const + { + return caCertificates_; + } + + void SetClientCertificate(const std::string& certificateFile, + const std::string& certificateKeyFile, + const std::string& certificateKeyPassword); + + void SetPkcs11Enabled(bool enabled) + { + pkcs11Enabled_ = enabled; + } + + bool IsPkcs11Enabled() const + { + return pkcs11Enabled_; + } + + const std::string& GetClientCertificateFile() const + { + return clientCertificateFile_; + } - static void GlobalInitialize(bool httpsVerifyPeers, - const std::string& httpsCACertificates); + const std::string& GetClientCertificateKeyFile() const + { + return clientCertificateKeyFile_; + } + + const std::string& GetClientCertificateKeyPassword() const + { + return clientCertificateKeyPassword_; + } + + void SetConvertHeadersToLowerCase(bool lowerCase) + { + headersToLowerCase_ = lowerCase; + } + + bool IsConvertHeadersToLowerCase() const + { + return headersToLowerCase_; + } + + void SetRedirectionFollowed(bool follow) + { + redirectionFollowed_ = follow; + } + + bool IsRedirectionFollowed() const + { + return redirectionFollowed_; + } + + static void GlobalInitialize(); static void GlobalFinalize(); + static void InitializePkcs11(const std::string& module, + const std::string& pin, + bool verbose); + + static void ConfigureSsl(bool httpsVerifyPeers, + const std::string& httpsCACertificates); + + static void SetDefaultVerbose(bool verbose); + + static void SetDefaultProxy(const std::string& proxy); + static void SetDefaultTimeout(long timeout); - void ApplyAndThrowException(std::string& answer); + void ApplyAndThrowException(std::string& answerBody); + + void ApplyAndThrowException(Json::Value& answerBody); - void ApplyAndThrowException(Json::Value& answer); + void ApplyAndThrowException(std::string& answerBody, + HttpHeaders& answerHeaders); + + void ApplyAndThrowException(Json::Value& answerBody, + HttpHeaders& answerHeaders); + + static void ThrowException(HttpStatus status); }; }
--- a/Core/HttpServer/BufferHttpSender.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/BufferHttpSender.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/BufferHttpSender.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,7 @@ #include "FilesystemHttpHandler.h" #include "../OrthancException.h" +#include "../SystemToolbox.h" #include "FilesystemHttpSender.h" #include <boost/filesystem.hpp> @@ -95,8 +97,10 @@ #endif std::string h = Toolbox::FlattenUri(uri) + "/" + f; - if (fs::is_regular_file(it->status())) + if (SystemToolbox::IsRegularFile(it->path().string())) + { s += "<li><a href=\"" + h + "\">" + f + "</a></li>"; + } } s += " </ul>"; @@ -156,7 +160,7 @@ p /= uri[i]; } - if (fs::exists(p) && fs::is_regular_file(p)) + if (SystemToolbox::IsRegularFile(p.string())) { FilesystemHttpSender sender(p); output.Answer(sender); // TODO COMPRESSION
--- a/Core/HttpServer/FilesystemHttpHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +70,7 @@ throw OrthancException(ErrorCode_CorruptedFile); } - chunkSize_ = file_.gcount(); + chunkSize_ = static_cast<size_t>(file_.gcount()); return chunkSize_ > 0; }
--- a/Core/HttpServer/FilesystemHttpSender.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,12 +51,12 @@ void Initialize(const boost::filesystem::path& path); public: - FilesystemHttpSender(const std::string& path) + explicit FilesystemHttpSender(const std::string& path) { Initialize(path); } - FilesystemHttpSender(const boost::filesystem::path& path) + explicit FilesystemHttpSender(const boost::filesystem::path& path) { Initialize(path); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpContentNegociation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,266 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "HttpContentNegociation.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include <boost/lexical_cast.hpp> + +namespace Orthanc +{ + HttpContentNegociation::Handler::Handler(const std::string& type, + const std::string& subtype, + IHandler& handler) : + type_(type), + subtype_(subtype), + handler_(handler) + { + } + + + bool HttpContentNegociation::Handler::IsMatch(const std::string& type, + const std::string& subtype) const + { + if (type == "*" && subtype == "*") + { + return true; + } + + if (subtype == "*" && type == type_) + { + return true; + } + + return type == type_ && subtype == subtype_; + } + + + struct HttpContentNegociation::Reference : public boost::noncopyable + { + const Handler& handler_; + uint8_t level_; + float quality_; + + Reference(const Handler& handler, + const std::string& type, + const std::string& subtype, + float quality) : + handler_(handler), + quality_(quality) + { + if (type == "*" && subtype == "*") + { + level_ = 0; + } + else if (subtype == "*") + { + level_ = 1; + } + else + { + level_ = 2; + } + } + + bool operator< (const Reference& other) const + { + if (level_ < other.level_) + { + return true; + } + + if (level_ > other.level_) + { + return false; + } + + return quality_ < other.quality_; + } + }; + + + bool HttpContentNegociation::SplitPair(std::string& first /* out */, + std::string& second /* out */, + const std::string& source, + char separator) + { + size_t pos = source.find(separator); + + if (pos == std::string::npos) + { + return false; + } + else + { + first = Toolbox::StripSpaces(source.substr(0, pos)); + second = Toolbox::StripSpaces(source.substr(pos + 1)); + return true; + } + } + + + float HttpContentNegociation::GetQuality(const Tokens& parameters) + { + for (size_t i = 1; i < parameters.size(); i++) + { + std::string key, value; + if (SplitPair(key, value, parameters[i], '=') && + key == "q") + { + float quality; + bool ok = false; + + try + { + quality = boost::lexical_cast<float>(value); + ok = (quality >= 0.0f && quality <= 1.0f); + } + catch (boost::bad_lexical_cast&) + { + } + + if (ok) + { + return quality; + } + else + { + LOG(ERROR) << "Quality parameter out of range in a HTTP request (must be between 0 and 1): " << value; + throw OrthancException(ErrorCode_BadRequest); + } + } + } + + return 1.0f; // Default quality + } + + + void HttpContentNegociation::SelectBestMatch(std::auto_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)); + + if (best.get() == NULL || + *best < *match) + { + best = match; + } + } + + + void HttpContentNegociation::Register(const std::string& mime, + IHandler& handler) + { + std::string type, subtype; + + if (SplitPair(type, subtype, mime, '/') && + type != "*" && + subtype != "*") + { + handlers_.push_back(Handler(type, subtype, handler)); + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool HttpContentNegociation::Apply(const HttpHeaders& headers) + { + HttpHeaders::const_iterator accept = headers.find("accept"); + if (accept != headers.end()) + { + return Apply(accept->second); + } + else + { + return Apply("*/*"); + } + } + + + bool HttpContentNegociation::Apply(const std::string& accept) + { + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 + // https://en.wikipedia.org/wiki/Content_negotiation + // http://www.newmediacampaigns.com/blog/browser-rest-http-accept-headers + + Tokens mediaRanges; + Toolbox::TokenizeString(mediaRanges, accept, ','); + + std::auto_ptr<Reference> bestMatch; + + for (Tokens::const_iterator it = mediaRanges.begin(); + it != mediaRanges.end(); ++it) + { + Tokens parameters; + Toolbox::TokenizeString(parameters, *it, ';'); + + if (parameters.size() > 0) + { + float quality = GetQuality(parameters); + + std::string type, subtype; + if (SplitPair(type, subtype, parameters[0], '/')) + { + for (Handlers::const_iterator it2 = handlers_.begin(); + it2 != handlers_.end(); ++it2) + { + if (it2->IsMatch(type, subtype)) + { + SelectBestMatch(bestMatch, *it2, type, subtype, quality); + } + } + } + } + } + + if (bestMatch.get() == NULL) // No match was found + { + return false; + } + else + { + bestMatch->handler_.Call(); + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpContentNegociation.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 <memory> +#include <boost/noncopyable.hpp> +#include <map> +#include <list> +#include <string> +#include <vector> +#include <stdint.h> + + +namespace Orthanc +{ + class HttpContentNegociation : public boost::noncopyable + { + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class IHandler : public boost::noncopyable + { + public: + virtual ~IHandler() + { + } + + virtual void Handle(const std::string& type, + const std::string& subtype) = 0; + }; + + private: + struct Handler + { + std::string type_; + std::string subtype_; + IHandler& handler_; + + Handler(const std::string& type, + const std::string& subtype, + IHandler& handler); + + bool IsMatch(const std::string& type, + const std::string& subtype) const; + + void Call() const + { + handler_.Handle(type_, subtype_); + } + }; + + + struct Reference; + + typedef std::vector<std::string> Tokens; + typedef std::list<Handler> Handlers; + + Handlers handlers_; + + + static bool SplitPair(std::string& first /* out */, + std::string& second /* out */, + const std::string& source, + char separator); + + static float GetQuality(const Tokens& parameters); + + static void SelectBestMatch(std::auto_ptr<Reference>& best, + const Handler& handler, + const std::string& type, + const std::string& subtype, + float quality); + + public: + void Register(const std::string& mime, + IHandler& handler); + + bool Apply(const HttpHeaders& headers); + + bool Apply(const std::string& accept); + }; +}
--- a/Core/HttpServer/HttpFileSender.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpFileSender.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpFileSender.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpOutput.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -291,8 +292,7 @@ const char* message, size_t messageSize) { - if (status == HttpStatus_200_Ok || - status == HttpStatus_301_MovedPermanently || + if (status == HttpStatus_301_MovedPermanently || status == HttpStatus_401_Unauthorized || status == HttpStatus_405_MethodNotAllowed) { @@ -300,7 +300,6 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } - stateMachine_.ClearHeaders(); stateMachine_.SetHttpStatus(status); stateMachine_.SendBody(message, messageSize); } @@ -433,28 +432,103 @@ { if (!Toolbox::StartsWith(*it, "Set-Cookie: ")) { - LOG(ERROR) << "The only headers that can be set in multipart answers are Set-Cookie (here: " << *it << " is set)"; + LOG(ERROR) << "The only headers that can be set in multipart answers " + << "are Set-Cookie (here: " << *it << " is set)"; throw OrthancException(ErrorCode_BadSequenceOfCalls); } header += *it; } + /** + * Fix for issue 54 ("Decide what to do wrt. quoting of multipart + * answers"). The "type" parameter in the "Content-Type" HTTP + * header must be quoted if it contains a forward slash "/". This + * is necessary for DICOMweb compatibility with OsiriX, but breaks + * compatibility with old releases of the client in the Orthanc + * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine). + * + * Full history is available at the following locations: + * - In changeset 2248:69b0f4e8a49b: + * # hg history -v -r 2248 + * - https://bitbucket.org/sjodogne/orthanc/issues/54/ + * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ + **/ + std::string tmp; + if (contentType.find('/') == std::string::npos) + { + // No forward slash in the content type + tmp = contentType; + } + else + { + // Quote the content type because of the forward slash + tmp = "\"" + contentType + "\""; + } + multipartBoundary_ = Toolbox::GenerateUuid(); multipartContentType_ = contentType; - header += "Content-Type: multipart/related; type=multipart/" + subType + "; boundary=" + multipartBoundary_ + "\r\n\r\n"; + header += ("Content-Type: multipart/" + subType + "; type=" + + tmp + "; boundary=" + multipartBoundary_ + "\r\n\r\n"); stream_.Send(true, header.c_str(), header.size()); state_ = State_WritingMultipart; } - void HttpOutput::StateMachine::SendMultipartItem(const void* item, size_t length) + void HttpOutput::StateMachine::SendMultipartItem(const void* item, + size_t length, + const std::map<std::string, std::string>& headers) { - std::string header = "--" + multipartBoundary_ + "\n"; - header += "Content-Type: " + multipartContentType_ + "\n"; - header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\n"; - header += "MIME-Version: 1.0\n\n"; + if (state_ != State_WritingMultipart) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::string header = "--" + multipartBoundary_ + "\r\n"; + + bool hasContentType = false; + bool hasContentLength = false; + bool hasMimeVersion = false; + + for (std::map<std::string, std::string>::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + header += it->first + ": " + it->second + "\r\n"; + + std::string tmp; + Toolbox::ToLowerCase(tmp, it->first); + + if (tmp == "content-type") + { + hasContentType = true; + } + + if (tmp == "content-length") + { + hasContentLength = true; + } + + if (tmp == "mime-version") + { + hasMimeVersion = true; + } + } + + if (!hasContentType) + { + header += "Content-Type: " + multipartContentType_ + "\r\n"; + } + + if (!hasContentLength) + { + header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n"; + } + + if (!hasMimeVersion) + { + header += "MIME-Version: 1.0\r\n\r\n"; + } stream_.Send(false, header.c_str(), header.size()); @@ -463,7 +537,7 @@ stream_.Send(false, item, length); } - stream_.Send(false, "\n", 1); + stream_.Send(false, "\r\n", 2); } @@ -478,7 +552,7 @@ // closed the connection. Such an error is ignored. try { - std::string header = "--" + multipartBoundary_ + "--\n"; + std::string header = "--" + multipartBoundary_ + "--\r\n"; stream_.Send(false, header.c_str(), header.size()); } catch (OrthancException&) @@ -489,19 +563,6 @@ } - void HttpOutput::SendMultipartItem(const std::string& item) - { - if (item.size() > 0) - { - stateMachine_.SendMultipartItem(item.c_str(), item.size()); - } - else - { - stateMachine_.SendMultipartItem(NULL, 0); - } - } - - void HttpOutput::Answer(IHttpStreamAnswer& stream) { HttpCompression compression = stream.SetupHttpCompression(isGzipAllowed_, isDeflateAllowed_);
--- a/Core/HttpServer/HttpOutput.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpOutput.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,13 +33,14 @@ #pragma once +#include "../Enumerations.h" +#include "IHttpOutputStream.h" +#include "IHttpStreamAnswer.h" + #include <list> #include <string> #include <stdint.h> -#include "../Enumerations.h" -#include "IHttpOutputStream.h" -#include "IHttpStreamAnswer.h" -#include "../Uuid.h" +#include <map> namespace Orthanc { @@ -99,7 +101,9 @@ void StartMultipart(const std::string& subType, const std::string& contentType); - void SendMultipartItem(const void* item, size_t length); + void SendMultipartItem(const void* item, + size_t length, + const std::map<std::string, std::string>& headers); void CloseMultipart(); @@ -202,11 +206,11 @@ stateMachine_.StartMultipart(subType, contentType); } - void SendMultipartItem(const std::string& item); - - void SendMultipartItem(const void* item, size_t size) + void SendMultipartItem(const void* item, + size_t size, + const std::map<std::string, std::string>& headers) { - stateMachine_.SendMultipartItem(item, size); + stateMachine_.SendMultipartItem(item, size, headers); } void CloseMultipart()
--- a/Core/HttpServer/HttpStreamTranscoder.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpStreamTranscoder.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpStreamTranscoder.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +62,7 @@ sourceCompression_(compression), bytesToSkip_(0), skipped_(0), + currentChunkOffset_(0), ready_(false) { }
--- a/Core/HttpServer/HttpToolbox.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpToolbox.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +42,7 @@ #include "StringHttpOutput.h" -static const char* LOCALHOST = "localhost"; +static const char* LOCALHOST = "127.0.0.1"; @@ -201,10 +202,9 @@ bool HttpToolbox::SimpleGet(std::string& result, IHttpHandler& handler, RequestOrigin origin, - const std::string& uri) + const std::string& uri, + const IHttpHandler::Arguments& httpHeaders) { - IHttpHandler::Arguments headers; // No HTTP header - UriComponents curi; IHttpHandler::GetArguments getArguments; ParseGetQuery(curi, getArguments, uri.c_str()); @@ -213,7 +213,7 @@ HttpOutput http(stream, false /* no keep alive */); if (handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Get, curi, - headers, getArguments, NULL /* no body for GET */, 0)) + httpHeaders, getArguments, NULL /* no body for GET */, 0)) { stream.GetOutput(result); return true; @@ -225,6 +225,16 @@ } + bool HttpToolbox::SimpleGet(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri) + { + IHttpHandler::Arguments headers; // No HTTP header + return SimpleGet(result, handler, origin, uri, headers); + } + + static bool SimplePostOrPut(std::string& result, IHttpHandler& handler, RequestOrigin origin,
--- a/Core/HttpServer/HttpToolbox.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/HttpToolbox.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,6 +66,12 @@ RequestOrigin origin, const std::string& uri); + static bool SimpleGet(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri, + const IHttpHandler::Arguments& httpHeaders); + static bool SimplePost(std::string& result, IHttpHandler& handler, RequestOrigin origin,
--- a/Core/HttpServer/IHttpHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/IHttpHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +33,7 @@ #pragma once -#include "../Enumerations.h" +#include "../Toolbox.h" #include "HttpOutput.h" #include <map>
--- a/Core/HttpServer/IHttpOutputStream.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/IHttpOutputStream.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/IHttpStreamAnswer.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/HttpServer/IIncomingHttpRequestFilter.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IHttpHandler.h" + +namespace Orthanc +{ + class IIncomingHttpRequestFilter : public boost::noncopyable + { + public: + virtual ~IIncomingHttpRequestFilter() + { + } + + virtual bool IsAllowed(HttpMethod method, + const char* uri, + const char* ip, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::GetArguments& getArguments) = 0; + }; +}
--- a/Core/HttpServer/MongooseServer.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/MongooseServer.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +39,17 @@ #include "../Logging.h" #include "../ChunkedBuffer.h" #include "HttpToolbox.h" -#include "mongoose.h" + +#if ORTHANC_ENABLE_MONGOOSE == 1 +# include "mongoose.h" + +#elif ORTHANC_ENABLE_CIVETWEB == 1 +# include "civetweb.h" +# define MONGOOSE_USE_CALLBACKS 1 + +#else +# error "Either Mongoose or Civetweb must be enabled to compile this file" +#endif #include <algorithm> #include <string.h> @@ -49,14 +60,16 @@ #include <stdio.h> #include <boost/thread.hpp> -#if ORTHANC_SSL_ENABLED == 1 +#if !defined(ORTHANC_ENABLE_SSL) +# error The macro ORTHANC_ENABLE_SSL must be defined +#endif + +#if ORTHANC_ENABLE_SSL == 1 #include <openssl/opensslv.h> #endif #define ORTHANC_REALM "Orthanc Secure Area" -static const long LOCALHOST = (127ll << 24) + 1ll; - namespace Orthanc { @@ -279,7 +292,7 @@ { length = boost::lexical_cast<int>(cs->second); } - catch (boost::bad_lexical_cast) + catch (boost::bad_lexical_cast&) { return PostDataStatus_NoLength; } @@ -351,7 +364,7 @@ { fileSize = boost::lexical_cast<size_t>(fileSizeStr->second); } - catch (boost::bad_lexical_cast) + catch (boost::bad_lexical_cast&) { return PostDataStatus_Failure; } @@ -404,7 +417,7 @@ last = it; } } - catch (std::length_error) + catch (std::length_error&) { return PostDataStatus_Failure; } @@ -575,19 +588,30 @@ } - static void InternalCallback(struct mg_connection *connection, + static void InternalCallback(HttpOutput& output /* out */, + HttpMethod& method /* out */, + MongooseServer& server, + struct mg_connection *connection, const struct mg_request_info *request) { - MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data); - - MongooseOutputStream stream(connection); - HttpOutput output(stream, that->IsKeepAliveEnabled()); + bool localhost; +#if ORTHANC_ENABLE_MONGOOSE == 1 + static const long LOCALHOST = (127ll << 24) + 1ll; + localhost = (request->remote_ip == LOCALHOST); +#elif ORTHANC_ENABLE_CIVETWEB == 1 + // The "remote_ip" field of "struct mg_request_info" is tagged as + // deprecated in Civetweb, using "remote_addr" instead. + localhost = (std::string(request->remote_addr) == "127.0.0.1"); +#else +#error +#endif + // Check remote calls - if (!that->IsRemoteAccessAllowed() && - request->remote_ip != LOCALHOST) + if (!server.IsRemoteAccessAllowed() && + !localhost) { - output.SendUnauthorized(ORTHANC_REALM); + output.SendUnauthorized(server.GetRealm()); return; } @@ -597,11 +621,14 @@ for (int i = 0; i < request->num_headers; i++) { std::string name = request->http_headers[i].name; + std::string value = request->http_headers[i].value; + std::transform(name.begin(), name.end(), name.begin(), ::tolower); - headers.insert(std::make_pair(name, request->http_headers[i].value)); + headers.insert(std::make_pair(name, value)); + VLOG(1) << "HTTP header: [" << name << "]: [" << value << "]"; } - if (that->IsHttpCompressionEnabled()) + if (server.IsHttpCompressionEnabled()) { ConfigureHttpCompression(output, headers); } @@ -616,7 +643,7 @@ // Compute the HTTP method, taking method faking into consideration - HttpMethod method = HttpMethod_Get; + method = HttpMethod_Get; if (!ExtractMethod(method, request, headers, argumentsGET)) { output.SendStatus(HttpStatus_400_BadRequest); @@ -625,13 +652,15 @@ // Authenticate this connection - if (that->IsAuthenticationEnabled() && !IsAccessGranted(*that, headers)) + if (server.IsAuthenticationEnabled() && + !IsAccessGranted(server, headers)) { - output.SendUnauthorized(ORTHANC_REALM); + output.SendUnauthorized(server.GetRealm()); return; } - + +#if ORTHANC_ENABLE_MONGOOSE == 1 // Apply the filter, if it is installed char remoteIp[24]; sprintf(remoteIp, "%d.%d.%d.%d", @@ -639,15 +668,22 @@ reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]); +#elif ORTHANC_ENABLE_CIVETWEB == 1 + const char* remoteIp = request->remote_addr; +#else +#error +#endif std::string username = GetAuthenticatedUsername(headers); - const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter(); + IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); if (filter != NULL) { - if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str())) + if (!filter->IsAllowed(method, request->uri, remoteIp, + username.c_str(), headers, argumentsGET)) { - output.SendUnauthorized(ORTHANC_REALM); + //output.SendUnauthorized(server.GetRealm()); + output.SendStatus(HttpStatus_403_Forbidden); return; } } @@ -675,7 +711,7 @@ if (contentType.size() >= multipartLength && !memcmp(contentType.c_str(), multipart, multipartLength)) { - status = ParseMultipartPost(body, connection, headers, contentType, that->GetChunkStore()); + status = ParseMultipartPost(body, connection, headers, contentType, server.GetChunkStore()); } else { @@ -709,7 +745,7 @@ { Toolbox::SplitUriComponents(uri, request->uri); } - catch (OrthancException) + catch (OrthancException&) { output.SendStatus(HttpStatus_400_BadRequest); return; @@ -718,56 +754,111 @@ LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); + bool found = false; + if (server.HasHandler()) + { + found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), + method, uri, headers, argumentsGET, body.c_str(), body.size()); + } + + if (!found) + { + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + static void ProtectedCallback(struct mg_connection *connection, + const struct mg_request_info *request) + { try { - bool found = false; + void* that = NULL; + +#if ORTHANC_ENABLE_MONGOOSE == 1 + that = request->user_data; +#elif ORTHANC_ENABLE_CIVETWEB == 1 + // https://github.com/civetweb/civetweb/issues/409 + that = mg_get_user_data(mg_get_context(connection)); +#else +#error +#endif + + MongooseServer* server = reinterpret_cast<MongooseServer*>(that); + + if (server == NULL) + { + MongooseOutputStream stream(connection); + HttpOutput output(stream, false /* assume no keep-alive */); + output.SendStatus(HttpStatus_500_InternalServerError); + return; + } + + MongooseOutputStream stream(connection); + HttpOutput output(stream, server->IsKeepAliveEnabled()); + HttpMethod method = HttpMethod_Get; try { - if (that->HasHandler()) + try + { + InternalCallback(output, method, *server, connection, request); + } + catch (OrthancException&) + { + throw; // Pass the exception to the main handler below + } + // Now convert native exceptions as OrthancException + catch (boost::bad_lexical_cast&) { - found = that->GetHandler().Handle(output, RequestOrigin_Http, remoteIp, username.c_str(), - method, uri, headers, argumentsGET, body.c_str(), body.size()); + LOG(ERROR) << "Syntax error in some user-supplied data"; + throw OrthancException(ErrorCode_BadParameterType); + } + catch (std::runtime_error&) + { + // Presumably an error while parsing the JSON body + throw OrthancException(ErrorCode_BadRequest); + } + catch (std::bad_alloc&) + { + LOG(ERROR) << "The server hosting Orthanc is running out of memory"; + throw OrthancException(ErrorCode_NotEnoughMemory); + } + catch (...) + { + LOG(ERROR) << "An unhandled exception was generated inside the HTTP server"; + throw OrthancException(ErrorCode_InternalError); } } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_BadParameterType); - } - catch (std::runtime_error&) + catch (OrthancException& e) { - // Presumably an error while parsing the JSON body - throw OrthancException(ErrorCode_BadRequest); - } + assert(server != NULL); - if (!found) - { - throw OrthancException(ErrorCode_UnknownResource); + // Using this candidate handler results in an exception + try + { + if (server->GetExceptionFormatter() == NULL) + { + LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); + output.SendStatus(e.GetHttpStatus()); + } + else + { + server->GetExceptionFormatter()->Format(output, e, method, request->uri); + } + } + catch (OrthancException&) + { + // An exception here reflects the fact that the status code + // was already set by the HTTP handler. + } } } - catch (OrthancException& e) + catch (...) { - // Using this candidate handler results in an exception - try - { - if (that->GetExceptionFormatter() == NULL) - { - LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); - output.SendStatus(e.GetHttpStatus()); - } - else - { - that->GetExceptionFormatter()->Format(output, e, method, request->uri); - } - } - catch (OrthancException&) - { - // An exception here reflects the fact that the status code - // was already set by the HTTP handler. - } - - return; + // We should never arrive at this point, where it is even impossible to send an answer + LOG(ERROR) << "Catastrophic error inside the HTTP server, giving up"; } } @@ -779,7 +870,7 @@ { if (event == MG_NEW_REQUEST) { - InternalCallback(connection, request); + ProtectedCallback(connection, request); // Mark as processed return (void*) ""; @@ -793,9 +884,9 @@ #elif MONGOOSE_USE_CALLBACKS == 1 static int Callback(struct mg_connection *connection) { - struct mg_request_info *request = mg_get_request_info(connection); + const struct mg_request_info *request = mg_get_request_info(connection); - InternalCallback(connection, request); + ProtectedCallback(connection, request); return 1; // Do not let Mongoose handle the request by itself } @@ -826,8 +917,9 @@ keepAlive_ = false; httpCompression_ = true; exceptionFormatter_ = NULL; + realm_ = ORTHANC_REALM; -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 // Check for the Heartbleed exploit // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ && @@ -853,6 +945,14 @@ void MongooseServer::Start() { +#if ORTHANC_ENABLE_MONGOOSE == 1 + LOG(INFO) << "Starting embedded Web server using Mongoose"; +#elif ORTHANC_ENABLE_CIVETWEB == 1 + LOG(INFO) << "Starting embedded Web server using Civetweb"; +#else +#error +#endif + if (!IsRunning()) { std::string port = boost::lexical_cast<std::string>(port_); @@ -870,6 +970,11 @@ // https://groups.google.com/d/msg/orthanc-users/CKueKX0pJ9E/_UCbl8T-VjIJ "enable_keep_alive", (keepAlive_ ? "yes" : "no"), +#if ORTHANC_ENABLE_CIVETWEB == 1 + // https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md#enable_keep_alive-no + "keep_alive_timeout_ms", (keepAlive_ ? "500" : "0"), +#endif + // Set the SSL certificate, if any. This must be the last option. ssl_ ? "ssl_certificate" : NULL, certificate_.c_str(), @@ -893,6 +998,13 @@ { throw OrthancException(ErrorCode_HttpPortInUse); } + + LOG(WARNING) << "HTTP server listening on port: " << GetPortNumber() + << " (HTTPS encryption is " + << (IsSslEnabled() ? "enabled" : "disabled") + << ", remote access is " + << (IsRemoteAccessAllowed() ? "" : "not ") + << "allowed)"; } } @@ -928,7 +1040,7 @@ { Stop(); -#if ORTHANC_SSL_ENABLED == 0 +#if ORTHANC_ENABLE_SSL == 0 if (enabled) { throw OrthancException(ErrorCode_SslDisabled); @@ -947,7 +1059,7 @@ { Stop(); keepAlive_ = enabled; - LOG(WARNING) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled"); + LOG(INFO) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled"); }
--- a/Core/HttpServer/MongooseServer.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/MongooseServer.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +33,21 @@ #pragma once -#include "IHttpHandler.h" +#if !defined(ORTHANC_ENABLE_MONGOOSE) +# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to include this file +#endif + +#if !defined(ORTHANC_ENABLE_CIVETWEB) +# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to include this file +#endif + +#if (ORTHANC_ENABLE_MONGOOSE == 0 && \ + ORTHANC_ENABLE_CIVETWEB == 0) +# error Either ORTHANC_ENABLE_MONGOOSE or ORTHANC_ENABLE_CIVETWEB must be set to 1 +#endif + + +#include "IIncomingHttpRequestFilter.h" #include "../OrthancException.h" @@ -46,21 +61,7 @@ { class ChunkStore; - class IIncomingHttpRequestFilter - { - public: - virtual ~IIncomingHttpRequestFilter() - { - } - - virtual bool IsAllowed(HttpMethod method, - const char* uri, - const char* ip, - const char* username) const = 0; - }; - - - class IHttpExceptionFormatter + class IHttpExceptionFormatter : public boost::noncopyable { public: virtual ~IHttpExceptionFormatter() @@ -95,6 +96,7 @@ bool keepAlive_; bool httpCompression_; IHttpExceptionFormatter* exceptionFormatter_; + std::string realm_; bool IsRunning() const; @@ -161,7 +163,7 @@ void SetHttpCompressionEnabled(bool enabled); - const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const + IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const { return filter_; } @@ -187,5 +189,15 @@ { return exceptionFormatter_; } + + const std::string& GetRealm() const + { + return realm_; + } + + void SetRealm(const std::string& realm) + { + realm_ = realm; + } }; }
--- a/Core/HttpServer/StringHttpOutput.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/StringHttpOutput.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +40,18 @@ { void StringHttpOutput::OnHttpStatusReceived(HttpStatus status) { - if (status != HttpStatus_200_Ok) + switch (status) { - throw OrthancException(ErrorCode_BadRequest); + case HttpStatus_200_Ok: + found_ = true; + break; + + case HttpStatus_404_NotFound: + found_ = false; + break; + + default: + throw OrthancException(ErrorCode_BadRequest); } } @@ -52,4 +62,16 @@ buffer_.AddChunk(reinterpret_cast<const char*>(buffer), length); } } + + void StringHttpOutput::GetOutput(std::string& output) + { + if (found_) + { + buffer_.Flatten(output); + } + else + { + throw OrthancException(ErrorCode_UnknownResource); + } + } }
--- a/Core/HttpServer/StringHttpOutput.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/HttpServer/StringHttpOutput.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,16 +42,18 @@ class StringHttpOutput : public IHttpOutputStream { private: + bool found_; ChunkedBuffer buffer_; public: + StringHttpOutput() : found_(false) + { + } + virtual void OnHttpStatusReceived(HttpStatus status); virtual void Send(bool isHeader, const void* buffer, size_t length); - void GetOutput(std::string& output) - { - buffer_.Flatten(output); - } + void GetOutput(std::string& output); }; }
--- a/Core/ICommand.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IDynamicObject.h" - -namespace Orthanc -{ - /** - * This class is the base class for the "Command" design pattern. - * http://en.wikipedia.org/wiki/Command_pattern - **/ - class ICommand : public IDynamicObject - { - public: - virtual bool Execute() = 0; - }; -}
--- a/Core/IDynamicObject.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/IDynamicObject.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,4 +50,27 @@ { } }; + + /** + * This class is a simple implementation of a IDynamicObject that stores a single typed value + */ + template <typename T> + class SingleValueObject : public Orthanc::IDynamicObject + { + private: + T value_; + public: + SingleValueObject(const T& value) : + value_(value) + { + } + virtual ~SingleValueObject() + { + } + + const T& GetValue() const + { + return value_; + } + }; }
--- a/Core/Images/Font.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/Font.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +34,14 @@ #include "../PrecompiledHeaders.h" #include "Font.h" +#if !defined(ORTHANC_ENABLE_LOCALE) +# error ORTHANC_ENABLE_LOCALE must be defined to use this file +#endif + +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + #include "../Toolbox.h" #include "../OrthancException.h" @@ -132,12 +141,14 @@ } +#if ORTHANC_SANDBOXED == 0 void Font::LoadFromFile(const std::string& path) { std::string font; - Toolbox::ReadFile(font, path); + SystemToolbox::ReadFile(font, path); LoadFromMemory(font); } +#endif static unsigned int MyMin(unsigned int a, @@ -208,6 +219,7 @@ } case PixelFormat_RGBA32: + case PixelFormat_BGRA32: { assert(bpp == 4); @@ -245,14 +257,21 @@ { if (target.GetFormat() != PixelFormat_Grayscale8 && target.GetFormat() != PixelFormat_RGB24 && - target.GetFormat() != PixelFormat_RGBA32) + target.GetFormat() != PixelFormat_RGBA32 && + target.GetFormat() != PixelFormat_BGRA32) { throw OrthancException(ErrorCode_NotImplemented); } int a = x; +#if ORTHANC_ENABLE_LOCALE == 1 std::string s = Toolbox::ConvertFromUtf8(utf8, Encoding_Latin1); +#else + // If the locale support is disabled, simply drop non-ASCII + // characters from the source UTF-8 string + std::string s = Toolbox::ConvertToAscii(utf8); +#endif for (size_t i = 0; i < s.size(); i++) { @@ -294,7 +313,25 @@ uint8_t g, uint8_t b) const { - uint8_t color[4] = { r, g, b, 255 }; + uint8_t color[4]; + + switch (target.GetFormat()) + { + case PixelFormat_BGRA32: + color[0] = b; + color[1] = g; + color[2] = r; + color[3] = 255; + break; + + default: + color[0] = r; + color[1] = g; + color[2] = b; + color[3] = 255; + break; + } + DrawInternal(target, utf8, x, y, color); }
--- a/Core/Images/Font.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/Font.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +84,9 @@ void LoadFromMemory(const std::string& font); +#if ORTHANC_SANDBOXED == 0 void LoadFromFile(const std::string& path); +#endif const std::string& GetName() const {
--- a/Core/Images/FontRegistry.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/FontRegistry.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,20 +57,24 @@ } +#if ORTHANC_SANDBOXED == 0 void FontRegistry::AddFromFile(const std::string& path) { std::auto_ptr<Font> f(new Font); f->LoadFromFile(path); fonts_.push_back(f.release()); } +#endif +#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1 void FontRegistry::AddFromResource(EmbeddedResources::FileResourceId resource) { std::string content; EmbeddedResources::GetFileResource(content, resource); AddFromMemory(content); } +#endif const Font& FontRegistry::GetFont(size_t i) const
--- a/Core/Images/FontRegistry.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/FontRegistry.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +35,13 @@ #include "Font.h" -#include <EmbeddedResources.h> // Autogenerated file +#if !defined(ORTHANC_HAS_EMBEDDED_RESOURCES) +# error Macro ORTHANC_HAS_EMBEDDED_RESOURCES must be defined +#endif + +#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1 +# include <EmbeddedResources.h> // Autogenerated file +#endif namespace Orthanc { @@ -50,9 +57,13 @@ void AddFromMemory(const std::string& font); +#if ORTHANC_SANDBOXED == 0 void AddFromFile(const std::string& path); +#endif +#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1 void AddFromResource(EmbeddedResources::FileResourceId resource); +#endif size_t GetSize() const {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/IImageWriter.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IImageWriter.h" + +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + +namespace Orthanc +{ +#if ORTHANC_SANDBOXED == 0 + void IImageWriter::WriteToFileInternal(const std::string& path, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + std::string compressed; + WriteToMemoryInternal(compressed, width, height, pitch, format, buffer); + SystemToolbox::WriteFile(compressed, path); + } +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/IImageWriter.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h" + +#include <boost/noncopyable.hpp> + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +namespace Orthanc +{ + class IImageWriter : public boost::noncopyable + { + protected: + virtual void WriteToMemoryInternal(std::string& compressed, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) = 0; + +#if ORTHANC_SANDBOXED == 0 + virtual void WriteToFileInternal(const std::string& path, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); +#endif + + public: + virtual ~IImageWriter() + { + } + + virtual void WriteToMemory(std::string& compressed, + const ImageAccessor& accessor) + { + WriteToMemoryInternal(compressed, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + +#if ORTHANC_SANDBOXED == 0 + virtual void WriteToFile(const std::string& path, + const ImageAccessor& accessor) + { + WriteToFileInternal(path, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } +#endif + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/Image.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,61 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "Image.h" + +#include "ImageProcessing.h" + +#include <memory> + +namespace Orthanc +{ + Image::Image(PixelFormat format, + unsigned int width, + unsigned int height, + bool forceMinimalPitch) : + image_(format, width, height, forceMinimalPitch) + { + ImageAccessor accessor; + image_.GetWriteableAccessor(accessor); + + AssignWritable(format, width, height, accessor.GetPitch(), accessor.GetBuffer()); + } + + + Image* Image::Clone(const ImageAccessor& source) + { + std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false)); + ImageProcessing::Copy(*target, source); + return target.release(); + } +}
--- a/Core/Images/Image.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/Image.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,11 +46,9 @@ public: Image(PixelFormat format, unsigned int width, - unsigned int height) : - image_(format, width, height) - { - ImageAccessor accessor = image_.GetAccessor(); - AssignWritable(format, width, height, accessor.GetPitch(), accessor.GetBuffer()); - } + unsigned int height, + bool forceMinimalPitch); + + static Image* Clone(const ImageAccessor& source); }; }
--- a/Core/Images/ImageAccessor.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/ImageAccessor.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +66,7 @@ for (unsigned int x = 0; x < source.GetWidth(); x++, p++) { - s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " "; + s += boost::lexical_cast<std::string>(static_cast<double>(*p)) + " "; } target.AddChunk(s); @@ -121,7 +122,7 @@ { if (buffer_ != NULL) { - return reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_; + return buffer_ + y * pitch_; } else { @@ -143,7 +144,7 @@ if (buffer_ != NULL) { - return reinterpret_cast<uint8_t*>(buffer_) + y * pitch_; + return buffer_ + y * pitch_; } else { @@ -174,9 +175,12 @@ width_ = width; height_ = height; pitch_ = pitch; - buffer_ = const_cast<void*>(buffer); + buffer_ = reinterpret_cast<uint8_t*>(const_cast<void*>(buffer)); - assert(GetBytesPerPixel() * width_ <= pitch_); + if (GetBytesPerPixel() * width_ > pitch_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } } @@ -191,9 +195,25 @@ width_ = width; height_ = height; pitch_ = pitch; - buffer_ = buffer; + buffer_ = reinterpret_cast<uint8_t*>(buffer); + + if (GetBytesPerPixel() * width_ > pitch_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + - assert(GetBytesPerPixel() * width_ <= pitch_); + void ImageAccessor::GetWriteableAccessor(ImageAccessor& target) const + { + if (readOnly_) + { + throw OrthancException(ErrorCode_ReadOnly); + } + else + { + target.AssignWritable(format_, width_, height_, pitch_, buffer_); + } } @@ -211,10 +231,22 @@ ToMatlabStringInternal<uint16_t>(buffer, *this); break; + case PixelFormat_Grayscale32: + ToMatlabStringInternal<uint32_t>(buffer, *this); + break; + + case PixelFormat_Grayscale64: + ToMatlabStringInternal<uint64_t>(buffer, *this); + break; + case PixelFormat_SignedGrayscale16: ToMatlabStringInternal<int16_t>(buffer, *this); break; + case PixelFormat_Float32: + ToMatlabStringInternal<float>(buffer, *this); + break; + case PixelFormat_RGB24: RGB24ToMatlabString(buffer, *this); break; @@ -226,4 +258,58 @@ buffer.Flatten(target); } + + + void ImageAccessor::GetRegion(ImageAccessor& accessor, + unsigned int x, + unsigned int y, + unsigned int width, + unsigned int height) const + { + if (x + width > width_ || + y + height > height_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (width == 0 || + height == 0) + { + accessor.AssignWritable(format_, 0, 0, 0, NULL); + } + else + { + uint8_t* p = (buffer_ + + y * pitch_ + + x * GetBytesPerPixel()); + + if (readOnly_) + { + accessor.AssignReadOnly(format_, width, height, pitch_, p); + } + else + { + accessor.AssignWritable(format_, width, height, pitch_, p); + } + } + } + + + void ImageAccessor::SetFormat(PixelFormat format) + { + if (readOnly_) + { +#if ORTHANC_ENABLE_LOGGING == 1 + LOG(ERROR) << "Trying to modify the format of a read-only image"; +#endif + throw OrthancException(ErrorCode_ReadOnly); + } + + if (::Orthanc::GetBytesPerPixel(format) != ::Orthanc::GetBytesPerPixel(format_)) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + format_ = format; + } }
--- a/Core/Images/ImageAccessor.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/ImageAccessor.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,18 +36,40 @@ #include "../Enumerations.h" #include <string> +#include <stdint.h> +#include <boost/noncopyable.hpp> namespace Orthanc { - class ImageAccessor + class ImageAccessor : public boost::noncopyable { private: + template <Orthanc::PixelFormat Format> + friend struct ImageTraits; + bool readOnly_; PixelFormat format_; unsigned int width_; unsigned int height_; unsigned int pitch_; - void *buffer_; + uint8_t *buffer_; + + template <typename T> + const T& GetPixelUnchecked(unsigned int x, + unsigned int y) const + { + const uint8_t* row = reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_; + return reinterpret_cast<const T*>(row) [x]; + } + + + template <typename T> + T& GetPixelUnchecked(unsigned int x, + unsigned int y) + { + uint8_t* row = reinterpret_cast<uint8_t*>(buffer_) + y * pitch_; + return reinterpret_cast<T*>(row) [x]; + } public: ImageAccessor() @@ -112,12 +135,27 @@ unsigned int pitch, const void *buffer); + void GetReadOnlyAccessor(ImageAccessor& target) const + { + target.AssignReadOnly(format_, width_, height_, pitch_, buffer_); + } + void AssignWritable(PixelFormat format, unsigned int width, unsigned int height, unsigned int pitch, void *buffer); + void GetWriteableAccessor(ImageAccessor& target) const; + void ToMatlabString(std::string& target) const; + + void GetRegion(ImageAccessor& accessor, + unsigned int x, + unsigned int y, + unsigned int width, + unsigned int height) const; + + void SetFormat(PixelFormat format); }; }
--- a/Core/Images/ImageBuffer.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/ImageBuffer.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -87,7 +88,9 @@ ImageBuffer::ImageBuffer(PixelFormat format, unsigned int width, - unsigned int height) + unsigned int height, + bool forceMinimalPitch) : + forceMinimalPitch_(forceMinimalPitch) { Initialize(); SetWidth(width); @@ -137,34 +140,18 @@ } } - - ImageAccessor ImageBuffer::GetAccessor() - { - Allocate(); - - ImageAccessor accessor; - accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); - return accessor; - } - - - ImageAccessor ImageBuffer::GetConstAccessor() + + void ImageBuffer::GetReadOnlyAccessor(ImageAccessor& accessor) { Allocate(); - - ImageAccessor accessor; accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_); - return accessor; } - + - void ImageBuffer::SetMinimalPitchForced(bool force) + void ImageBuffer::GetWriteableAccessor(ImageAccessor& accessor) { - if (force != forceMinimalPitch_) - { - changed_ = true; - forceMinimalPitch_ = force; - } + Allocate(); + accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); }
--- a/Core/Images/ImageBuffer.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/ImageBuffer.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +62,8 @@ public: ImageBuffer(PixelFormat format, unsigned int width, - unsigned int height); + unsigned int height, + bool forceMinimalPitch); ImageBuffer() { @@ -99,17 +101,15 @@ return ::Orthanc::GetBytesPerPixel(format_); } - ImageAccessor GetAccessor(); + void GetReadOnlyAccessor(ImageAccessor& accessor); - ImageAccessor GetConstAccessor(); + void GetWriteableAccessor(ImageAccessor& accessor); bool IsMinimalPitchForced() const { return forceMinimalPitch_; } - void SetMinimalPitchForced(bool force); - void AcquireOwnership(ImageBuffer& other); }; }
--- a/Core/Images/ImageProcessing.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/ImageProcessing.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ #include "../PrecompiledHeaders.h" #include "ImageProcessing.h" -#include "../OrthancException.h" +#include "PixelTraits.h" #include <boost/math/special_functions/round.hpp> @@ -75,9 +76,28 @@ } + template <typename SourceType> + static void ConvertGrayscaleToFloat(ImageAccessor& target, + const ImageAccessor& source) + { + assert(sizeof(float) == 4); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + float* t = reinterpret_cast<float*>(target.GetRow(y)); + const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++) + { + *t = static_cast<float>(*s); + } + } + } + + template <typename TargetType> static void ConvertColorToGrayscale(ImageAccessor& target, - const ImageAccessor& source) + const ImageAccessor& source) { assert(source.GetFormat() == PixelFormat_RGB24); @@ -94,7 +114,7 @@ // Y = 0.2126 R + 0.7152 G + 0.0722 B int32_t v = (2126 * static_cast<int32_t>(s[0]) + 7152 * static_cast<int32_t>(s[1]) + - 0722 * static_cast<int32_t>(s[2])) / 1000; + 0722 * static_cast<int32_t>(s[2])) / 10000; if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue)) { @@ -146,11 +166,13 @@ minValue = std::numeric_limits<PixelType>::max(); maxValue = std::numeric_limits<PixelType>::min(); + const unsigned int width = source.GetWidth(); + for (unsigned int y = 0; y < source.GetHeight(); y++) { const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); - for (unsigned int x = 0; x < source.GetWidth(); x++, p++) + for (unsigned int x = 0; x < width; x++, p++) { if (*p < minValue) { @@ -205,9 +227,10 @@ - template <typename PixelType> - void MultiplyConstantInternal(ImageAccessor& image, - float factor) + template <typename PixelType, + bool UseRound> + static void MultiplyConstantInternal(ImageAccessor& image, + float factor) { if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon()) { @@ -216,14 +239,24 @@ const int64_t minValue = std::numeric_limits<PixelType>::min(); const int64_t maxValue = std::numeric_limits<PixelType>::max(); + const unsigned int width = image.GetWidth(); for (unsigned int y = 0; y < image.GetHeight(); y++) { PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); - for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + for (unsigned int x = 0; x < width; x++, p++) { - int64_t v = boost::math::llround(static_cast<float>(*p) * factor); + int64_t v; + if (UseRound) + { + // The "round" operation is very costly + v = boost::math::llround(static_cast<float>(*p) * factor); + } + else + { + v = static_cast<int64_t>(static_cast<float>(*p) * factor); + } if (v > maxValue) { @@ -242,33 +275,44 @@ } - template <typename PixelType> - void ShiftScaleInternal(ImageAccessor& image, - float offset, - float scaling) + template <typename PixelType, + bool UseRound> + static void ShiftScaleInternal(ImageAccessor& image, + float offset, + float scaling) { - const float minValue = static_cast<float>(std::numeric_limits<PixelType>::min()); - const float maxValue = static_cast<float>(std::numeric_limits<PixelType>::max()); + const float minFloatValue = static_cast<float>(std::numeric_limits<PixelType>::min()); + const float maxFloatValue = static_cast<float>(std::numeric_limits<PixelType>::max()); + const PixelType minPixelValue = std::numeric_limits<PixelType>::min(); + const PixelType maxPixelValue = std::numeric_limits<PixelType>::max(); - for (unsigned int y = 0; y < image.GetHeight(); y++) + 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 < image.GetWidth(); x++, p++) + for (unsigned int x = 0; x < width; x++, p++) { float v = (static_cast<float>(*p) + offset) * scaling; - if (v > maxValue) + if (v > maxFloatValue) { - *p = std::numeric_limits<PixelType>::max(); + *p = maxPixelValue; } - else if (v < minValue) + else if (v < minFloatValue) { - *p = std::numeric_limits<PixelType>::min(); + *p = minPixelValue; + } + else if (UseRound) + { + // The "round" operation is very costly + *p = static_cast<PixelType>(boost::math::iround(v)); } else { - *p = static_cast<PixelType>(boost::math::iround(v)); + *p = static_cast<PixelType>(v); } } } @@ -378,6 +422,34 @@ return; } + if (target.GetFormat() == PixelFormat_Float32 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertGrayscaleToFloat<uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Float32 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertGrayscaleToFloat<uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Float32 && + source.GetFormat() == PixelFormat_Grayscale32) + { + ConvertGrayscaleToFloat<uint32_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Float32 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertGrayscaleToFloat<int16_t>(target, source); + return; + } + if (target.GetFormat() == PixelFormat_Grayscale8 && source.GetFormat() == PixelFormat_RGBA32) { @@ -397,6 +469,25 @@ return; } + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_BGRA32) + { + for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++, q++) + { + *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[2]) + + 7152 * static_cast<uint32_t>(p[1]) + + 0722 * static_cast<uint32_t>(p[0])) / 10000); + p += 4; + } + } + + return; + } + if (target.GetFormat() == PixelFormat_RGB24 && source.GetFormat() == PixelFormat_RGBA32) { @@ -417,6 +508,26 @@ return; } + if (target.GetFormat() == PixelFormat_RGB24 && + source.GetFormat() == PixelFormat_BGRA32) + { + for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++) + { + q[0] = p[2]; + q[1] = p[1]; + q[2] = p[0]; + p += 4; + q += 3; + } + } + + return; + } + if (target.GetFormat() == PixelFormat_RGBA32 && source.GetFormat() == PixelFormat_RGB24) { @@ -438,6 +549,146 @@ return; } + if (target.GetFormat() == PixelFormat_RGB24 && + source.GetFormat() == PixelFormat_Grayscale8) + { + for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++) + { + q[0] = *p; + q[1] = *p; + q[2] = *p; + p += 1; + q += 3; + } + } + + return; + } + + if ((target.GetFormat() == PixelFormat_RGBA32 || + target.GetFormat() == PixelFormat_BGRA32) && + source.GetFormat() == PixelFormat_Grayscale8) + { + for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++) + { + q[0] = *p; + q[1] = *p; + q[2] = *p; + q[3] = 255; + p += 1; + q += 4; + } + } + + return; + } + + if (target.GetFormat() == PixelFormat_BGRA32 && + source.GetFormat() == PixelFormat_Grayscale16) + { + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + uint8_t value = (*p < 256 ? *p : 255); + q[0] = value; + q[1] = value; + q[2] = value; + q[3] = 255; + p += 1; + q += 4; + } + } + + return; + } + + if (target.GetFormat() == PixelFormat_BGRA32 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const int16_t* p = reinterpret_cast<const int16_t*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + uint8_t value; + if (*p < 0) + { + value = 0; + } + else if (*p > 255) + { + value = 255; + } + else + { + value = static_cast<uint8_t>(*p); + } + + q[0] = value; + q[1] = value; + q[2] = value; + q[3] = 255; + p += 1; + q += 4; + } + } + + return; + } + + if (target.GetFormat() == PixelFormat_BGRA32 && + source.GetFormat() == PixelFormat_RGB24) + { + for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++) + { + q[0] = p[2]; + q[1] = p[1]; + q[2] = p[0]; + q[3] = 255; + p += 3; + q += 4; + } + } + + return; + } + + if (target.GetFormat() == PixelFormat_RGB24 && + source.GetFormat() == PixelFormat_RGB48) + { + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + q[0] = p[0] >> 8; + q[1] = p[1] >> 8; + q[2] = p[2] >> 8; + p += 3; + q += 3; + } + } + + return; + } + throw OrthancException(ErrorCode_NotImplemented); } @@ -449,15 +700,56 @@ switch (image.GetFormat()) { case PixelFormat_Grayscale8: - SetInternal<uint8_t>(image, value); + memset(image.GetBuffer(), static_cast<uint8_t>(value), image.GetPitch() * image.GetHeight()); return; case PixelFormat_Grayscale16: - SetInternal<uint16_t>(image, value); + if (value == 0) + { + memset(image.GetBuffer(), 0, image.GetPitch() * image.GetHeight()); + } + else + { + SetInternal<uint16_t>(image, value); + } + return; + + case PixelFormat_Grayscale32: + if (value == 0) + { + memset(image.GetBuffer(), 0, image.GetPitch() * image.GetHeight()); + } + else + { + SetInternal<uint32_t>(image, value); + } + return; + + case PixelFormat_Grayscale64: + if (value == 0) + { + memset(image.GetBuffer(), 0, image.GetPitch() * image.GetHeight()); + } + else + { + SetInternal<uint64_t>(image, value); + } return; case PixelFormat_SignedGrayscale16: - SetInternal<int16_t>(image, value); + if (value == 0) + { + memset(image.GetBuffer(), 0, image.GetPitch() * image.GetHeight()); + } + else + { + SetInternal<int16_t>(image, value); + } + return; + + case PixelFormat_Float32: + assert(sizeof(float) == 4); + SetInternal<float>(image, value); return; default: @@ -466,6 +758,61 @@ } + void ImageProcessing::Set(ImageAccessor& image, + uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t alpha) + { + uint8_t p[4]; + unsigned int size; + + switch (image.GetFormat()) + { + case PixelFormat_RGBA32: + p[0] = red; + p[1] = green; + p[2] = blue; + p[3] = alpha; + size = 4; + break; + + case PixelFormat_BGRA32: + p[0] = blue; + p[1] = green; + p[2] = red; + p[3] = alpha; + size = 4; + break; + + case PixelFormat_RGB24: + p[0] = red; + p[1] = green; + p[2] = blue; + size = 3; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++) + { + for (unsigned int i = 0; i < size; i++) + { + q[i] = p[i]; + } + + q += size; + } + } + } + + void ImageProcessing::ShiftRight(ImageAccessor& image, unsigned int shift) { @@ -481,9 +828,9 @@ } - void ImageProcessing::GetMinMaxValue(int64_t& minValue, - int64_t& maxValue, - const ImageAccessor& image) + void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image) { switch (image.GetFormat()) { @@ -505,6 +852,15 @@ break; } + case PixelFormat_Grayscale32: + { + uint32_t a, b; + GetMinMaxValueInternal<uint32_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + case PixelFormat_SignedGrayscale16: { int16_t a, b; @@ -520,6 +876,28 @@ } + void ImageProcessing::GetMinMaxFloatValue(float& minValue, + float& maxValue, + const ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Float32: + { + assert(sizeof(float) == 32); + float a, b; + GetMinMaxValueInternal<float>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + void ImageProcessing::AddConstant(ImageAccessor& image, int64_t value) @@ -545,20 +923,42 @@ void ImageProcessing::MultiplyConstant(ImageAccessor& image, - float factor) + float factor, + bool useRound) { switch (image.GetFormat()) { case PixelFormat_Grayscale8: - MultiplyConstantInternal<uint8_t>(image, factor); + if (useRound) + { + MultiplyConstantInternal<uint8_t, true>(image, factor); + } + else + { + MultiplyConstantInternal<uint8_t, false>(image, factor); + } return; case PixelFormat_Grayscale16: - MultiplyConstantInternal<uint16_t>(image, factor); + if (useRound) + { + MultiplyConstantInternal<uint16_t, true>(image, factor); + } + else + { + MultiplyConstantInternal<uint16_t, false>(image, factor); + } return; case PixelFormat_SignedGrayscale16: - MultiplyConstantInternal<int16_t>(image, factor); + if (useRound) + { + MultiplyConstantInternal<int16_t, true>(image, factor); + } + else + { + MultiplyConstantInternal<int16_t, false>(image, factor); + } return; default: @@ -569,24 +969,302 @@ void ImageProcessing::ShiftScale(ImageAccessor& image, float offset, - float scaling) + float scaling, + bool useRound) { switch (image.GetFormat()) { case PixelFormat_Grayscale8: - ShiftScaleInternal<uint8_t>(image, offset, scaling); + if (useRound) + { + ShiftScaleInternal<uint8_t, true>(image, offset, scaling); + } + else + { + ShiftScaleInternal<uint8_t, false>(image, offset, scaling); + } return; case PixelFormat_Grayscale16: - ShiftScaleInternal<uint16_t>(image, offset, scaling); + if (useRound) + { + ShiftScaleInternal<uint16_t, true>(image, offset, scaling); + } + else + { + ShiftScaleInternal<uint16_t, false>(image, offset, scaling); + } return; case PixelFormat_SignedGrayscale16: - ShiftScaleInternal<int16_t>(image, offset, scaling); + if (useRound) + { + ShiftScaleInternal<int16_t, true>(image, offset, scaling); + } + else + { + ShiftScaleInternal<int16_t, false>(image, offset, scaling); + } return; default: throw OrthancException(ErrorCode_NotImplemented); } } + + + void ImageProcessing::Invert(ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = 255 - (*p); + } + } + + return; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + + namespace + { + template <Orthanc::PixelFormat Format> + class BresenhamPixelWriter + { + private: + typedef typename PixelTraits<Format>::PixelType PixelType; + + Orthanc::ImageAccessor& image_; + PixelType value_; + + void PlotLineLow(int x0, + int y0, + int x1, + int y1) + { + int dx = x1 - x0; + int dy = y1 - y0; + int yi = 1; + + if (dy < 0) + { + yi = -1; + dy = -dy; + } + + int d = 2 * dy - dx; + int y = y0; + + for (int x = x0; x <= x1; x++) + { + Write(x, y); + + if (d > 0) + { + y = y + yi; + d = d - 2 * dx; + } + + d = d + 2*dy; + } + } + + void PlotLineHigh(int x0, + int y0, + int x1, + int y1) + { + int dx = x1 - x0; + int dy = y1 - y0; + int xi = 1; + + if (dx < 0) + { + xi = -1; + dx = -dx; + } + + int d = 2 * dx - dy; + int x = x0; + + for (int y = y0; y <= y1; y++) + { + Write(x, y); + + if (d > 0) + { + x = x + xi; + d = d - 2 * dy; + } + + d = d + 2 * dx; + } + } + + public: + BresenhamPixelWriter(Orthanc::ImageAccessor& image, + int64_t value) : + image_(image), + value_(PixelTraits<Format>::IntegerToPixel(value)) + { + } + + BresenhamPixelWriter(Orthanc::ImageAccessor& image, + const PixelType& value) : + image_(image), + value_(value) + { + } + + void Write(int x, + int y) + { + if (x >= 0 && + y >= 0 && + static_cast<unsigned int>(x) < image_.GetWidth() && + static_cast<unsigned int>(y) < image_.GetHeight()) + { + PixelType* p = reinterpret_cast<PixelType*>(image_.GetRow(y)); + p[x] = value_; + } + } + + void DrawSegment(int x0, + int y0, + int x1, + int y1) + { + // This is an implementation of Bresenham's line algorithm + // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#All_cases + + if (abs(y1 - y0) < abs(x1 - x0)) + { + if (x0 > x1) + { + PlotLineLow(x1, y1, x0, y0); + } + else + { + PlotLineLow(x0, y0, x1, y1); + } + } + else + { + if (y0 > y1) + { + PlotLineHigh(x1, y1, x0, y0); + } + else + { + PlotLineHigh(x0, y0, x1, y1); + } + } + } + }; + } + + + void ImageProcessing::DrawLineSegment(ImageAccessor& image, + int x0, + int y0, + int x1, + int y1, + int64_t value) + { + switch (image.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + { + BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale8> writer(image, value); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + case Orthanc::PixelFormat_Grayscale16: + { + BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale16> writer(image, value); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + case Orthanc::PixelFormat_SignedGrayscale16: + { + BresenhamPixelWriter<Orthanc::PixelFormat_SignedGrayscale16> writer(image, value); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::DrawLineSegment(ImageAccessor& image, + int x0, + int y0, + int x1, + int y1, + uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t alpha) + { + switch (image.GetFormat()) + { + case Orthanc::PixelFormat_BGRA32: + { + PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType pixel; + pixel.red_ = red; + pixel.green_ = green; + pixel.blue_ = blue; + pixel.alpha_ = alpha; + + BresenhamPixelWriter<Orthanc::PixelFormat_BGRA32> writer(image, pixel); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + case Orthanc::PixelFormat_RGBA32: + { + PixelTraits<Orthanc::PixelFormat_RGBA32>::PixelType pixel; + pixel.red_ = red; + pixel.green_ = green; + pixel.blue_ = blue; + pixel.alpha_ = alpha; + + BresenhamPixelWriter<Orthanc::PixelFormat_RGBA32> writer(image, pixel); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + case Orthanc::PixelFormat_RGB24: + { + PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel; + pixel.red_ = red; + pixel.green_ = green; + pixel.blue_ = blue; + + BresenhamPixelWriter<Orthanc::PixelFormat_RGB24> writer(image, pixel); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } }
--- a/Core/Images/ImageProcessing.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/ImageProcessing.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,33 +39,65 @@ namespace Orthanc { - class ImageProcessing + namespace ImageProcessing { - public: - static void Copy(ImageAccessor& target, - const ImageAccessor& source); + void Copy(ImageAccessor& target, + const ImageAccessor& source); + + void Convert(ImageAccessor& target, + const ImageAccessor& source); + + void Set(ImageAccessor& image, + int64_t value); - static void Convert(ImageAccessor& target, - const ImageAccessor& source); + void Set(ImageAccessor& image, + uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t alpha); - static void Set(ImageAccessor& image, - int64_t value); + void ShiftRight(ImageAccessor& target, + unsigned int shift); - static void ShiftRight(ImageAccessor& target, - unsigned int shift); - - static void GetMinMaxValue(int64_t& minValue, + void GetMinMaxIntegerValue(int64_t& minValue, int64_t& maxValue, const ImageAccessor& image); - static void AddConstant(ImageAccessor& image, - int64_t value); + void GetMinMaxFloatValue(float& minValue, + float& maxValue, + const ImageAccessor& image); + + void AddConstant(ImageAccessor& image, + int64_t value); - static void MultiplyConstant(ImageAccessor& image, - float factor); + // "useRound" is expensive + void MultiplyConstant(ImageAccessor& image, + float factor, + bool useRound); + + // "useRound" is expensive + void ShiftScale(ImageAccessor& image, + float offset, + float scaling, + bool useRound); - static void ShiftScale(ImageAccessor& image, - float offset, - float scaling); - }; + void Invert(ImageAccessor& image); + + void DrawLineSegment(ImageAccessor& image, + int x0, + int y0, + int x1, + int y1, + int64_t value); + + void DrawLineSegment(ImageAccessor& image, + int x0, + int y0, + int x1, + int y1, + uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t alpha); + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/ImageTraits.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h" +#include "PixelTraits.h" + +#include <cassert> + +namespace Orthanc +{ + template <PixelFormat Format> + struct ImageTraits + { + typedef ::Orthanc::PixelTraits<Format> PixelTraits; + typedef typename PixelTraits::PixelType PixelType; + + static PixelFormat GetPixelFormat() + { + return Format; + } + + static void GetPixel(PixelType& target, + const ImageAccessor& image, + unsigned int x, + unsigned int y) + { + assert(x < image.GetWidth() && y < image.GetHeight()); + PixelTraits::Copy(target, image.GetPixelUnchecked<PixelType>(x, y)); + } + + static void SetPixel(ImageAccessor& image, + const PixelType& value, + unsigned int x, + unsigned int y) + { + assert(x < image.GetWidth() && y < image.GetHeight()); + PixelTraits::Copy(image.GetPixelUnchecked<PixelType>(x, y), value); + } + + static float GetFloatPixel(const ImageAccessor& image, + unsigned int x, + unsigned int y) + { + assert(x < image.GetWidth() && y < image.GetHeight()); + return PixelTraits::PixelToFloat(image.GetPixelUnchecked<PixelType>(x, y)); + } + + static void SetFloatPixel(ImageAccessor& image, + float value, + unsigned int x, + unsigned int y) + { + assert(x < image.GetWidth() && y < image.GetHeight()); + PixelTraits::FloatToPixel(image.GetPixelUnchecked<PixelType>(x, y), value); + } + }; +}
--- a/Core/Images/JpegErrorManager.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/JpegErrorManager.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/JpegErrorManager.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -31,6 +32,14 @@ #pragma once +#if !defined(ORTHANC_ENABLE_JPEG) +# error The macro ORTHANC_ENABLE_JPEG must be defined +#endif + +#if ORTHANC_ENABLE_JPEG != 1 +# error JPEG support must be enabled to include this file +#endif + #include <string.h> #include <stdio.h> #include <jpeglib.h>
--- a/Core/Images/JpegReader.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/JpegReader.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,13 +38,21 @@ #include "../OrthancException.h" #include "../Logging.h" +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + + namespace Orthanc { static void Uncompress(struct jpeg_decompress_struct& cinfo, std::string& content, ImageAccessor& accessor) { - jpeg_read_header(&cinfo, TRUE); + // The "static_cast" is necessary on OS X: + // https://github.com/simonfuhrmann/mve/issues/371 + jpeg_read_header(&cinfo, static_cast<boolean>(true)); + jpeg_start_decompress(&cinfo); PixelFormat format; @@ -93,9 +102,10 @@ } - void JpegReader::ReadFromFile(const char* filename) +#if ORTHANC_SANDBOXED == 0 + void JpegReader::ReadFromFile(const std::string& filename) { - FILE* fp = fopen(filename, "rb"); + FILE* fp = SystemToolbox::OpenFile(filename, FileMode_ReadBinary); if (!fp) { throw OrthancException(ErrorCode_InexistentFile); @@ -111,7 +121,7 @@ { jpeg_destroy_decompress(&cinfo); fclose(fp); - LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage(); + LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage(); throw OrthancException(ErrorCode_InternalError); } @@ -134,6 +144,7 @@ jpeg_destroy_decompress(&cinfo); fclose(fp); } +#endif void JpegReader::ReadFromMemory(const void* buffer, @@ -148,7 +159,7 @@ if (setjmp(jerr.GetJumpBuffer())) { jpeg_destroy_decompress(&cinfo); - LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage(); + LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage(); throw OrthancException(ErrorCode_InternalError); }
--- a/Core/Images/JpegReader.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/JpegReader.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +33,18 @@ #pragma once +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if !defined(ORTHANC_ENABLE_JPEG) +# error The macro ORTHANC_ENABLE_JPEG must be defined +#endif + +#if ORTHANC_ENABLE_JPEG != 1 +# error JPEG support must be enabled to include this file +#endif + #include "ImageAccessor.h" #include <string> @@ -44,12 +57,9 @@ std::string content_; public: - void ReadFromFile(const char* filename); - - void ReadFromFile(const std::string& filename) - { - ReadFromFile(filename.c_str()); - } +#if ORTHANC_SANDBOXED == 0 + void ReadFromFile(const std::string& filename); +#endif void ReadFromMemory(const void* buffer, size_t size);
--- a/Core/Images/JpegWriter.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/JpegWriter.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +36,13 @@ #include "../OrthancException.h" #include "../Logging.h" - #include "JpegErrorManager.h" +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + +#include <stdlib.h> #include <vector> namespace Orthanc @@ -91,8 +96,12 @@ } jpeg_set_defaults(&cinfo); - jpeg_set_quality(&cinfo, quality, TRUE); - jpeg_start_compress(&cinfo, TRUE); + + // The "static_cast" is necessary on OS X: + // https://github.com/simonfuhrmann/mve/issues/371 + jpeg_set_quality(&cinfo, quality, static_cast<boolean>(true)); + jpeg_start_compress(&cinfo, static_cast<boolean>(true)); + jpeg_write_scanlines(&cinfo, &lines[0], height); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); @@ -101,7 +110,7 @@ void JpegWriter::SetQuality(uint8_t quality) { - if (quality <= 0 || quality > 100) + if (quality == 0 || quality > 100) { throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -110,17 +119,18 @@ } - void JpegWriter::WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) +#if ORTHANC_SANDBOXED == 0 + void JpegWriter::WriteToFileInternal(const std::string& filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) { - FILE* fp = fopen(filename, "wb"); + FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary); if (fp == NULL) { - throw OrthancException(ErrorCode_FullStorage); + throw OrthancException(ErrorCode_CannotWriteFile); } std::vector<uint8_t*> lines; @@ -153,14 +163,15 @@ fclose(fp); } +#endif - void JpegWriter::WriteToMemory(std::string& jpeg, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) + void JpegWriter::WriteToMemoryInternal(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) { std::vector<uint8_t*> lines; GetLines(lines, height, pitch, format, buffer);
--- a/Core/Images/JpegWriter.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/JpegWriter.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +33,37 @@ #pragma once -#include "ImageAccessor.h" +#if !defined(ORTHANC_ENABLE_JPEG) +# error The macro ORTHANC_ENABLE_JPEG must be defined +#endif -#include <string> -#include <stdint.h> +#if ORTHANC_ENABLE_JPEG != 1 +# error JPEG support must be enabled to include this file +#endif + +#include "IImageWriter.h" namespace Orthanc { - class JpegWriter + class JpegWriter : public IImageWriter { + protected: +#if ORTHANC_SANDBOXED == 0 + virtual void WriteToFileInternal(const std::string& filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); +#endif + + virtual void WriteToMemoryInternal(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + private: uint8_t quality_; @@ -55,33 +78,5 @@ { return quality_; } - - void WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToMemory(std::string& jpeg, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToFile(const char* filename, - const ImageAccessor& accessor) - { - WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } - - void WriteToMemory(std::string& jpeg, - const ImageAccessor& accessor) - { - WriteToMemory(jpeg, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamReader.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,247 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "PamReader.h" + +#include "../Endianness.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + +#include <boost/algorithm/string/find.hpp> +#include <boost/lexical_cast.hpp> + + +namespace Orthanc +{ + static void GetPixelFormat(PixelFormat& format, + unsigned int& bytesPerChannel, + const unsigned int& maxValue, + const unsigned int& channelCount, + const std::string& tupleType) + { + if (tupleType == "GRAYSCALE" && + channelCount == 1) + { + switch (maxValue) + { + case 255: + format = PixelFormat_Grayscale8; + bytesPerChannel = 1; + return; + + case 65535: + format = PixelFormat_Grayscale16; + bytesPerChannel = 2; + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + else if (tupleType == "RGB" && + channelCount == 3) + { + switch (maxValue) + { + case 255: + format = PixelFormat_RGB24; + bytesPerChannel = 1; + return; + + case 65535: + format = PixelFormat_RGB48; + bytesPerChannel = 2; + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + typedef std::map<std::string, std::string> Parameters; + + + static std::string LookupStringParameter(const Parameters& parameters, + const std::string& key) + { + Parameters::const_iterator found = parameters.find(key); + + if (found == parameters.end()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return found->second; + } + } + + + static unsigned int LookupIntegerParameter(const Parameters& parameters, + const std::string& key) + { + try + { + int value = boost::lexical_cast<int>(LookupStringParameter(parameters, key)); + + if (value < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return static_cast<unsigned int>(value); + } + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void PamReader::ParseContent() + { + static const std::string headerDelimiter = "ENDHDR\n"; + + boost::iterator_range<std::string::const_iterator> headerRange = + boost::algorithm::find_first(content_, headerDelimiter); + + if (!headerRange) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::string header(static_cast<const std::string&>(content_).begin(), headerRange.begin()); + + std::vector<std::string> lines; + Toolbox::TokenizeString(lines, header, '\n'); + + if (lines.size() < 2 || + lines.front() != "P7" || + !lines.back().empty()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Parameters parameters; + + for (size_t i = 1; i + 1 < lines.size(); i++) + { + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, lines[i], ' '); + + if (tokens.size() != 2) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + parameters[tokens[0]] = tokens[1]; + } + } + + const unsigned int width = LookupIntegerParameter(parameters, "WIDTH"); + const unsigned int height = LookupIntegerParameter(parameters, "HEIGHT"); + const unsigned int channelCount = LookupIntegerParameter(parameters, "DEPTH"); + const unsigned int maxValue = LookupIntegerParameter(parameters, "MAXVAL"); + const std::string tupleType = LookupStringParameter(parameters, "TUPLTYPE"); + + unsigned int bytesPerChannel; + PixelFormat format; + GetPixelFormat(format, bytesPerChannel, maxValue, channelCount, tupleType.c_str()); + + unsigned int pitch = width * channelCount * bytesPerChannel; + + if (content_.size() != header.size() + headerDelimiter.size() + pitch * height) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + size_t offset = content_.size() - pitch * height; + AssignWritable(format, width, height, pitch, &content_[offset]); + + // Byte swapping if needed + if (bytesPerChannel != 1 && + bytesPerChannel != 2) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if (Toolbox::DetectEndianness() == Endianness_Little && + bytesPerChannel == 2) + { + for (unsigned int h = 0; h < height; ++h) + { + uint16_t* pixel = reinterpret_cast<uint16_t*>(GetRow(h)); + + for (unsigned int w = 0; w < GetWidth(); ++w, ++pixel) + { + // memcpy() is necessary to avoid segmentation fault if the + // "pixel" pointer is not 16-bit aligned (which is the case + // if "offset" is an odd number). Check out issue #99: + // https://bitbucket.org/sjodogne/orthanc/issues/99 + uint16_t v = htobe16(*pixel); + memcpy(pixel, &v, sizeof(v)); + } + } + } + } + + +#if ORTHANC_SANDBOXED == 0 + void PamReader::ReadFromFile(const std::string& filename) + { + SystemToolbox::ReadFile(content_, filename); + ParseContent(); + } +#endif + + + void PamReader::ReadFromMemory(const std::string& buffer) + { + content_ = buffer; + ParseContent(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamReader.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h" + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +namespace Orthanc +{ + class PamReader : public ImageAccessor + { + private: + void ParseContent(); + + std::string content_; + + public: +#if ORTHANC_SANDBOXED == 0 + void ReadFromFile(const std::string& filename); +#endif + + void ReadFromMemory(const std::string& buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamWriter.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,159 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "PamWriter.h" + +#include "../Endianness.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include <boost/lexical_cast.hpp> + + +namespace Orthanc +{ + static void GetPixelFormatInfo(const PixelFormat& format, + unsigned int& maxValue, + unsigned int& channelCount, + unsigned int& bytesPerChannel, + std::string& tupleType) + { + switch (format) + { + case PixelFormat_Grayscale8: + maxValue = 255; + channelCount = 1; + bytesPerChannel = 1; + tupleType = "GRAYSCALE"; + break; + + case PixelFormat_Grayscale16: + maxValue = 65535; + channelCount = 1; + bytesPerChannel = 2; + tupleType = "GRAYSCALE"; + break; + + case PixelFormat_RGB24: + maxValue = 255; + channelCount = 3; + bytesPerChannel = 1; + tupleType = "RGB"; + break; + + case PixelFormat_RGB48: + maxValue = 255; + channelCount = 3; + bytesPerChannel = 2; + tupleType = "RGB"; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void PamWriter::WriteToMemoryInternal(std::string& target, + unsigned int width, + unsigned int height, + unsigned int sourcePitch, + PixelFormat format, + const void* buffer) + { + unsigned int maxValue, channelCount, bytesPerChannel; + std::string tupleType; + GetPixelFormatInfo(format, maxValue, channelCount, bytesPerChannel, tupleType); + + target = (std::string("P7") + + std::string("\nWIDTH ") + boost::lexical_cast<std::string>(width) + + std::string("\nHEIGHT ") + boost::lexical_cast<std::string>(height) + + std::string("\nDEPTH ") + boost::lexical_cast<std::string>(channelCount) + + std::string("\nMAXVAL ") + boost::lexical_cast<std::string>(maxValue) + + std::string("\nTUPLTYPE ") + tupleType + + std::string("\nENDHDR\n")); + + if (bytesPerChannel != 1 && + bytesPerChannel != 2) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + size_t targetPitch = channelCount * bytesPerChannel * width; + size_t offset = target.size(); + + target.resize(offset + targetPitch * height); + + assert(target.size() != 0); + + if (Toolbox::DetectEndianness() == Endianness_Little && + bytesPerChannel == 2) + { + // Byte swapping + for (unsigned int h = 0; h < height; ++h) + { + const uint16_t* p = reinterpret_cast<const uint16_t*> + (reinterpret_cast<const uint8_t*>(buffer) + h * sourcePitch); + uint16_t* q = reinterpret_cast<uint16_t*> + (reinterpret_cast<uint8_t*>(&target[offset]) + h * targetPitch); + + for (unsigned int w = 0; w < width * channelCount; ++w) + { + // memcpy() is necessary to avoid segmentation fault if the + // "pixel" pointer is not 16-bit aligned (which is the case + // if "offset" is an odd number). Check out issue #99: + // https://bitbucket.org/sjodogne/orthanc/issues/99 + uint16_t v = htobe16(*p); + memcpy(q, &v, sizeof(uint16_t)); + + p++; + q++; + } + } + } + else + { + // Either "bytesPerChannel == 1" (and endianness is not + // relevant), or we run on a big endian architecture (and no + // byte swapping is necessary, as PAM uses big endian) + + for (unsigned int h = 0; h < height; ++h) + { + const void* p = reinterpret_cast<const uint8_t*>(buffer) + h * sourcePitch; + void* q = reinterpret_cast<uint8_t*>(&target[offset]) + h * targetPitch; + memcpy(q, p, targetPitch); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PamWriter.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,51 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IImageWriter.h" + +namespace Orthanc +{ + // https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format + class PamWriter : public IImageWriter + { + protected: + virtual void WriteToMemoryInternal(std::string& target, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PixelTraits.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,395 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" +#include "../OrthancException.h" + +#include <limits> + +namespace Orthanc +{ + template <PixelFormat format, + typename _PixelType> + struct IntegerPixelTraits + { + typedef _PixelType PixelType; + + ORTHANC_FORCE_INLINE + static PixelFormat GetPixelFormat() + { + return format; + } + + ORTHANC_FORCE_INLINE + static PixelType IntegerToPixel(int64_t value) + { + if (value < static_cast<int64_t>(std::numeric_limits<PixelType>::min()) || + value > static_cast<int64_t>(std::numeric_limits<PixelType>::max())) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return static_cast<PixelType>(value); + } + } + + ORTHANC_FORCE_INLINE + static void SetZero(PixelType& target) + { + target = 0; + } + + ORTHANC_FORCE_INLINE + static void SetMinValue(PixelType& target) + { + target = std::numeric_limits<PixelType>::min(); + } + + ORTHANC_FORCE_INLINE + static void SetMaxValue(PixelType& target) + { + target = std::numeric_limits<PixelType>::max(); + } + + ORTHANC_FORCE_INLINE + static void Copy(PixelType& target, + const PixelType& source) + { + target = source; + } + + ORTHANC_FORCE_INLINE + static float PixelToFloat(const PixelType& source) + { + return static_cast<float>(source); + } + + ORTHANC_FORCE_INLINE + static void FloatToPixel(PixelType& target, + float value) + { + if (value < static_cast<float>(std::numeric_limits<PixelType>::min())) + { + target = std::numeric_limits<PixelType>::min(); + } + else if (value > static_cast<float>(std::numeric_limits<PixelType>::max())) + { + target = std::numeric_limits<PixelType>::max(); + } + else + { + target = static_cast<PixelType>(value); + } + } + + ORTHANC_FORCE_INLINE + static bool IsEqual(const PixelType& a, + const PixelType& b) + { + return a == b; + } + }; + + + template <PixelFormat Format> + struct PixelTraits; + + + template <> + struct PixelTraits<PixelFormat_Grayscale8> : + public IntegerPixelTraits<PixelFormat_Grayscale8, uint8_t> + { + }; + + + template <> + struct PixelTraits<PixelFormat_Grayscale16> : + public IntegerPixelTraits<PixelFormat_Grayscale16, uint16_t> + { + }; + + + template <> + struct PixelTraits<PixelFormat_SignedGrayscale16> : + public IntegerPixelTraits<PixelFormat_SignedGrayscale16, int16_t> + { + }; + + + template <> + struct PixelTraits<PixelFormat_Grayscale32> : + public IntegerPixelTraits<PixelFormat_Grayscale32, uint32_t> + { + }; + + + template <> + struct PixelTraits<PixelFormat_Grayscale64> : + public IntegerPixelTraits<PixelFormat_Grayscale64, uint64_t> + { + }; + + + template <> + struct PixelTraits<PixelFormat_RGB24> + { + struct PixelType + { + uint8_t red_; + uint8_t green_; + uint8_t blue_; + }; + + ORTHANC_FORCE_INLINE + static PixelFormat GetPixelFormat() + { + return PixelFormat_RGB24; + } + + ORTHANC_FORCE_INLINE + static void SetZero(PixelType& target) + { + target.red_ = 0; + target.green_ = 0; + target.blue_ = 0; + } + + ORTHANC_FORCE_INLINE + static void Copy(PixelType& target, + const PixelType& source) + { + target.red_ = source.red_; + target.green_ = source.green_; + target.blue_ = source.blue_; + } + + ORTHANC_FORCE_INLINE + static bool IsEqual(const PixelType& a, + const PixelType& b) + { + return (a.red_ == b.red_ && + a.green_ == b.green_ && + a.blue_ == b.blue_); + } + + ORTHANC_FORCE_INLINE + static void FloatToPixel(PixelType& target, + float value) + { + uint8_t v; + PixelTraits<PixelFormat_Grayscale8>::FloatToPixel(v, value); + + target.red_ = v; + target.green_ = v; + target.blue_ = v; + } + }; + + + template <> + struct PixelTraits<PixelFormat_BGRA32> + { + struct PixelType + { + uint8_t blue_; + uint8_t green_; + uint8_t red_; + uint8_t alpha_; + }; + + ORTHANC_FORCE_INLINE + static PixelFormat GetPixelFormat() + { + return PixelFormat_BGRA32; + } + + ORTHANC_FORCE_INLINE + static void SetZero(PixelType& target) + { + target.blue_ = 0; + target.green_ = 0; + target.red_ = 0; + target.alpha_ = 0; + } + + ORTHANC_FORCE_INLINE + static void Copy(PixelType& target, + const PixelType& source) + { + target.blue_ = source.blue_; + target.green_ = source.green_; + target.red_ = source.red_; + target.alpha_ = source.alpha_; + } + + ORTHANC_FORCE_INLINE + static bool IsEqual(const PixelType& a, + const PixelType& b) + { + return (a.blue_ == b.blue_ && + a.green_ == b.green_ && + a.red_ == b.red_ && + a.alpha_ == b.alpha_); + } + + ORTHANC_FORCE_INLINE + static void FloatToPixel(PixelType& target, + float value) + { + uint8_t v; + PixelTraits<PixelFormat_Grayscale8>::FloatToPixel(v, value); + + target.blue_ = v; + target.green_ = v; + target.red_ = v; + target.alpha_ = 255; + } + }; + + + template <> + struct PixelTraits<PixelFormat_RGBA32> + { + struct PixelType + { + uint8_t red_; + uint8_t green_; + uint8_t blue_; + uint8_t alpha_; + }; + + ORTHANC_FORCE_INLINE + static PixelFormat GetPixelFormat() + { + return PixelFormat_RGBA32; + } + + ORTHANC_FORCE_INLINE + static void SetZero(PixelType& target) + { + target.red_ = 0; + target.green_ = 0; + target.blue_ = 0; + target.alpha_ = 0; + } + + ORTHANC_FORCE_INLINE + static void Copy(PixelType& target, + const PixelType& source) + { + target.red_ = source.red_; + target.green_ = source.green_; + target.blue_ = source.blue_; + target.alpha_ = source.alpha_; + } + + ORTHANC_FORCE_INLINE + static bool IsEqual(const PixelType& a, + const PixelType& b) + { + return (a.red_ == b.red_ && + a.green_ == b.green_ && + a.blue_ == b.blue_ && + a.alpha_ == b.alpha_); + } + + ORTHANC_FORCE_INLINE + static void FloatToPixel(PixelType& target, + float value) + { + uint8_t v; + PixelTraits<PixelFormat_Grayscale8>::FloatToPixel(v, value); + + target.red_ = v; + target.green_ = v; + target.blue_ = v; + target.alpha_ = 255; + } + }; + + + template <> + struct PixelTraits<PixelFormat_Float32> + { + typedef float PixelType; + + ORTHANC_FORCE_INLINE + static PixelFormat GetPixelFormat() + { + return PixelFormat_Float32; + } + + ORTHANC_FORCE_INLINE + static void SetZero(PixelType& target) + { + target = 0.0f; + } + + ORTHANC_FORCE_INLINE + static void Copy(PixelType& target, + const PixelType& source) + { + target = source; + } + + ORTHANC_FORCE_INLINE + static bool IsEqual(const PixelType& a, + const PixelType& b) + { + float tmp = (a - b); + + if (tmp < 0) + { + tmp = -tmp; + } + + return tmp <= std::numeric_limits<float>::epsilon(); + } + + ORTHANC_FORCE_INLINE + static void FloatToPixel(PixelType& target, + float value) + { + target = value; + } + + ORTHANC_FORCE_INLINE + static float PixelToFloat(const PixelType& source) + { + return source; + } + }; +}
--- a/Core/Images/PngReader.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/PngReader.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,11 +37,16 @@ #include "../OrthancException.h" #include "../Toolbox.h" +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + #include <png.h> #include <string.h> // For memcpy() namespace Orthanc { +#if ORTHANC_SANDBOXED == 0 namespace { struct FileRabi @@ -49,7 +55,7 @@ FileRabi(const char* filename) { - fp_ = fopen(filename, "rb"); + fp_ = SystemToolbox::OpenFile(filename, FileMode_ReadBinary); if (!fp_) { throw OrthancException(ErrorCode_InexistentFile); @@ -59,10 +65,13 @@ ~FileRabi() { if (fp_) + { fclose(fp_); + } } }; } +#endif struct PngReader::PngRabi @@ -204,9 +213,11 @@ AssignWritable(format, width, height, pitch, &data_[0]); } - void PngReader::ReadFromFile(const char* filename) + +#if ORTHANC_SANDBOXED == 0 + void PngReader::ReadFromFile(const std::string& filename) { - FileRabi f(filename); + FileRabi f(filename.c_str()); char header[8]; if (fread(header, 1, 8, f.fp_) != 8) @@ -228,6 +239,7 @@ Read(rabi); } +#endif namespace
--- a/Core/Images/PngReader.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/PngReader.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +33,14 @@ #pragma once +#if !defined(ORTHANC_ENABLE_PNG) +# error The macro ORTHANC_ENABLE_PNG must be defined +#endif + +#if ORTHANC_ENABLE_PNG != 1 +# error PNG support must be enabled to include this file +#endif + #include "ImageAccessor.h" #include "../Enumerations.h" @@ -40,6 +49,10 @@ #include <stdint.h> #include <boost/shared_ptr.hpp> +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + namespace Orthanc { class PngReader : public ImageAccessor @@ -56,12 +69,9 @@ public: PngReader(); - void ReadFromFile(const char* filename); - - void ReadFromFile(const std::string& filename) - { - ReadFromFile(filename.c_str()); - } +#if ORTHANC_SANDBOXED == 0 + void ReadFromFile(const std::string& filename); +#endif void ReadFromMemory(const void* buffer, size_t size);
--- a/Core/Images/PngWriter.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/PngWriter.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,6 +41,10 @@ #include "../ChunkedBuffer.h" #include "../Toolbox.h" +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 // http://zarb.org/~gc/html/libpng.html @@ -202,16 +207,17 @@ } - void PngWriter::WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) +#if ORTHANC_SANDBOXED == 0 + void PngWriter::WriteToFileInternal(const std::string& filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) { Prepare(width, height, pitch, format, buffer); - FILE* fp = fopen(filename, "wb"); + FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary); if (!fp) { throw OrthancException(ErrorCode_CannotWriteFile); @@ -229,8 +235,7 @@ fclose(fp); } - - +#endif static void MemoryCallback(png_structp png_ptr, @@ -243,12 +248,12 @@ - void PngWriter::WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) + void PngWriter::WriteToMemoryInternal(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) { ChunkedBuffer chunks;
--- a/Core/Images/PngWriter.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Images/PngWriter.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +33,39 @@ #pragma once -#include "ImageAccessor.h" +#if !defined(ORTHANC_ENABLE_PNG) +# error The macro ORTHANC_ENABLE_PNG must be defined +#endif + +#if ORTHANC_ENABLE_PNG != 1 +# error PNG support must be enabled to include this file +#endif + +#include "IImageWriter.h" #include <boost/shared_ptr.hpp> -#include <string> namespace Orthanc { - class PngWriter + class PngWriter : public IImageWriter { + protected: +#if ORTHANC_SANDBOXED == 0 + virtual void WriteToFileInternal(const std::string& filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); +#endif + + virtual void WriteToMemoryInternal(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + private: struct PImpl; boost::shared_ptr<PImpl> pimpl_; @@ -60,33 +85,5 @@ PngWriter(); ~PngWriter(); - - void WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToFile(const char* filename, - const ImageAccessor& accessor) - { - WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } - - void WriteToMemory(std::string& png, - const ImageAccessor& accessor) - { - WriteToMemory(png, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "GenericJobUnserializer.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../SerializationToolbox.h" + +#include "Operations/LogJobOperation.h" +#include "Operations/NullOperationValue.h" +#include "Operations/SequenceOfOperationsJob.h" +#include "Operations/StringOperationValue.h" + +namespace Orthanc +{ + IJob* GenericJobUnserializer::UnserializeJob(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "SequenceOfOperations") + { + return new SequenceOfOperationsJob(*this, source); + } + else + { + LOG(ERROR) << "Cannot unserialize job of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + IJobOperation* GenericJobUnserializer::UnserializeOperation(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "Log") + { + return new LogJobOperation; + } + else + { + LOG(ERROR) << "Cannot unserialize operation of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + JobOperationValue* GenericJobUnserializer::UnserializeValue(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "String") + { + return new StringOperationValue(SerializationToolbox::ReadString(source, "Content")); + } + else if (type == "Null") + { + return new NullOperationValue; + } + else + { + LOG(ERROR) << "Cannot unserialize value of type: " << type; + throw OrthancException(ErrorCode_BadFileFormat); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/GenericJobUnserializer.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IJobUnserializer.h" + +namespace Orthanc +{ + class GenericJobUnserializer : public IJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& value); + + virtual IJobOperation* UnserializeOperation(const Json::Value& value); + + virtual JobOperationValue* UnserializeValue(const Json::Value& value); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobStepResult.h" + +#include <boost/noncopyable.hpp> +#include <json/value.h> + +namespace Orthanc +{ + class IJob : public boost::noncopyable + { + public: + virtual ~IJob() + { + } + + // Method called once the job enters the jobs engine + virtual void Start() = 0; + + virtual JobStepResult Step() = 0; + + // Method called once the job is resubmitted after a failure + virtual void Reset() = 0; + + // For pausing/canceling/ending jobs: This method must release allocated resources + virtual void Stop(JobStopReason reason) = 0; + + virtual float GetProgress() = 0; + + virtual void GetJobType(std::string& target) = 0; + + virtual void GetPublicContent(Json::Value& value) = 0; + + virtual bool Serialize(Json::Value& value) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/IJobUnserializer.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IJob.h" +#include "Operations/JobOperationValue.h" +#include "Operations/IJobOperation.h" + +#include <vector> + +namespace Orthanc +{ + class IJobUnserializer : public boost::noncopyable + { + public: + virtual ~IJobUnserializer() + { + } + + virtual IJob* UnserializeJob(const Json::Value& value) = 0; + + virtual IJobOperation* UnserializeOperation(const Json::Value& value) = 0; + + virtual JobOperationValue* UnserializeValue(const Json::Value& value) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,148 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobInfo.h" + +#include "../OrthancException.h" + +// This "include" is mandatory for Release builds using Linux Standard Base +#include <boost/math/special_functions/round.hpp> + +namespace Orthanc +{ + JobInfo::JobInfo(const std::string& id, + int priority, + JobState state, + const JobStatus& status, + const boost::posix_time::ptime& creationTime, + const boost::posix_time::ptime& lastStateChangeTime, + const boost::posix_time::time_duration& runtime) : + id_(id), + priority_(priority), + state_(state), + timestamp_(boost::posix_time::microsec_clock::universal_time()), + creationTime_(creationTime), + lastStateChangeTime_(lastStateChangeTime), + runtime_(runtime), + hasEta_(false), + status_(status) + { + if (state_ == JobState_Running) + { + float ms = static_cast<float>(runtime_.total_milliseconds()); + + if (status_.GetProgress() > 0.01f && + ms > 0.01f) + { + float ratio = static_cast<float>(1.0 - status_.GetProgress()); + long long remaining = boost::math::llround(ratio * ms); + eta_ = timestamp_ + boost::posix_time::milliseconds(remaining); + hasEta_ = true; + } + } + } + + + JobInfo::JobInfo() : + priority_(0), + state_(JobState_Failure), + timestamp_(boost::posix_time::microsec_clock::universal_time()), + creationTime_(timestamp_), + lastStateChangeTime_(timestamp_), + runtime_(boost::posix_time::milliseconds(0)), + hasEta_(false) + { + } + + + bool JobInfo::HasCompletionTime() const + { + return (state_ == JobState_Success || + state_ == JobState_Failure); + } + + + const boost::posix_time::ptime& JobInfo::GetEstimatedTimeOfArrival() const + { + if (hasEta_) + { + return eta_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + const boost::posix_time::ptime& JobInfo::GetCompletionTime() const + { + if (HasCompletionTime()) + { + return lastStateChangeTime_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + void JobInfo::Format(Json::Value& target) const + { + target = Json::objectValue; + target["ID"] = id_; + target["Priority"] = priority_; + target["ErrorCode"] = static_cast<int>(status_.GetErrorCode()); + target["ErrorDescription"] = EnumerationToString(status_.GetErrorCode()); + target["State"] = EnumerationToString(state_); + target["Timestamp"] = boost::posix_time::to_iso_string(timestamp_); + target["CreationTime"] = boost::posix_time::to_iso_string(creationTime_); + target["EffectiveRuntime"] = static_cast<double>(runtime_.total_milliseconds()) / 1000.0; + target["Progress"] = boost::math::iround(status_.GetProgress() * 100.0f); + + target["Type"] = status_.GetJobType(); + target["Content"] = status_.GetPublicContent(); + + if (HasEstimatedTimeOfArrival()) + { + target["EstimatedTimeOfArrival"] = boost::posix_time::to_iso_string(GetEstimatedTimeOfArrival()); + } + + if (HasCompletionTime()) + { + target["CompletionTime"] = boost::posix_time::to_iso_string(GetCompletionTime()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobInfo.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,120 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobStatus.h" + +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace Orthanc +{ + class JobInfo + { + private: + std::string id_; + int priority_; + JobState state_; + boost::posix_time::ptime timestamp_; + boost::posix_time::ptime creationTime_; + boost::posix_time::ptime lastStateChangeTime_; + boost::posix_time::time_duration runtime_; + bool hasEta_; + boost::posix_time::ptime eta_; + JobStatus status_; + + public: + JobInfo(const std::string& id, + int priority, + JobState state, + const JobStatus& status, + const boost::posix_time::ptime& creationTime, + const boost::posix_time::ptime& lastStateChangeTime, + const boost::posix_time::time_duration& runtime); + + JobInfo(); + + const std::string& GetIdentifier() const + { + return id_; + } + + int GetPriority() const + { + return priority_; + } + + JobState GetState() const + { + return state_; + } + + const boost::posix_time::ptime& GetInfoTime() const + { + return timestamp_; + } + + const boost::posix_time::ptime& GetCreationTime() const + { + return creationTime_; + } + + const boost::posix_time::time_duration& GetRuntime() const + { + return runtime_; + } + + bool HasEstimatedTimeOfArrival() const + { + return hasEta_; + } + + bool HasCompletionTime() const; + + const boost::posix_time::ptime& GetEstimatedTimeOfArrival() const; + + const boost::posix_time::ptime& GetCompletionTime() const; + + const JobStatus& GetStatus() const + { + return status_; + } + + JobStatus& GetStatus() + { + return status_; + } + + void Format(Json::Value& target) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "JobStatus.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + JobStatus::JobStatus() : + errorCode_(ErrorCode_InternalError), + progress_(0), + jobType_("Invalid"), + publicContent_(Json::objectValue), + hasSerialized_(false) + { + } + + + JobStatus::JobStatus(ErrorCode code, + IJob& job) : + errorCode_(code), + progress_(job.GetProgress()), + publicContent_(Json::objectValue) + { + if (progress_ < 0) + { + progress_ = 0; + } + + if (progress_ > 1) + { + progress_ = 1; + } + + job.GetJobType(jobType_); + job.GetPublicContent(publicContent_); + + hasSerialized_ = job.Serialize(serialized_); + } + + + const Json::Value& JobStatus::GetSerialized() const + { + if (!hasSerialized_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return serialized_; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStatus.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,88 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IJob.h" + +namespace Orthanc +{ + class JobStatus + { + private: + ErrorCode errorCode_; + float progress_; + std::string jobType_; + Json::Value publicContent_; + Json::Value serialized_; + bool hasSerialized_; + + public: + JobStatus(); + + JobStatus(ErrorCode code, + IJob& job); + + ErrorCode GetErrorCode() const + { + return errorCode_; + } + + void SetErrorCode(ErrorCode error) + { + errorCode_ = error; + } + + float GetProgress() const + { + return progress_; + } + + const std::string& GetJobType() const + { + return jobType_; + } + + const Json::Value& GetPublicContent() const + { + return publicContent_; + } + + const Json::Value& GetSerialized() const; + + bool HasSerialized() const + { + return hasSerialized_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobStepResult.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + JobStepResult JobStepResult::Retry(unsigned int timeout) + { + JobStepResult result(JobStepCode_Retry); + result.timeout_ = timeout; + return result; + } + + + JobStepResult JobStepResult::Failure(const ErrorCode& error) + { + JobStepResult result(JobStepCode_Failure); + result.error_ = error; + return result; + } + + + unsigned int JobStepResult::GetRetryTimeout() const + { + if (code_ == JobStepCode_Retry) + { + return timeout_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + ErrorCode JobStepResult::GetFailureCode() const + { + if (code_ == JobStepCode_Failure) + { + return error_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobStepResult.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +namespace Orthanc +{ + class JobStepResult + { + private: + JobStepCode code_; + unsigned int timeout_; + ErrorCode error_; + + explicit JobStepResult(JobStepCode code) : + code_(code), + timeout_(0), + error_(ErrorCode_Success) + { + } + + public: + explicit JobStepResult() : + code_(JobStepCode_Failure), + timeout_(0), + error_(ErrorCode_InternalError) + { + } + + static JobStepResult Success() + { + return JobStepResult(JobStepCode_Success); + } + + static JobStepResult Continue() + { + return JobStepResult(JobStepCode_Continue); + } + + static JobStepResult Retry(unsigned int timeout); + + static JobStepResult Failure(const ErrorCode& error); + + JobStepCode GetCode() const + { + return code_; + } + + unsigned int GetRetryTimeout() const; + + ErrorCode GetFailureCode() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,324 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobsEngine.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +#include <json/reader.h> + +namespace Orthanc +{ + bool JobsEngine::IsRunning() + { + boost::mutex::scoped_lock lock(stateMutex_); + return (state_ == State_Running); + } + + + bool JobsEngine::ExecuteStep(JobsRegistry::RunningJob& running, + size_t workerIndex) + { + assert(running.IsValid()); + + if (running.IsPauseScheduled()) + { + running.GetJob().Stop(JobStopReason_Paused); + running.MarkPause(); + return false; + } + + if (running.IsCancelScheduled()) + { + running.GetJob().Stop(JobStopReason_Canceled); + running.MarkCanceled(); + return false; + } + + JobStepResult result; + + try + { + result = running.GetJob().Step(); + } + catch (OrthancException& e) + { + result = JobStepResult::Failure(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + result = JobStepResult::Failure(ErrorCode_BadFileFormat); + } + catch (...) + { + result = JobStepResult::Failure(ErrorCode_InternalError); + } + + switch (result.GetCode()) + { + case JobStepCode_Success: + running.GetJob().Stop(JobStopReason_Success); + running.UpdateStatus(ErrorCode_Success); + running.MarkSuccess(); + return false; + + case JobStepCode_Failure: + running.GetJob().Stop(JobStopReason_Failure); + running.UpdateStatus(result.GetFailureCode()); + running.MarkFailure(); + return false; + + case JobStepCode_Retry: + running.GetJob().Stop(JobStopReason_Retry); + running.UpdateStatus(ErrorCode_Success); + running.MarkRetry(result.GetRetryTimeout()); + return false; + + case JobStepCode_Continue: + running.UpdateStatus(ErrorCode_Success); + return true; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + void JobsEngine::RetryHandler(JobsEngine* engine) + { + assert(engine != NULL); + + while (engine->IsRunning()) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(engine->threadSleep_)); + engine->GetRegistry().ScheduleRetries(); + } + } + + + void JobsEngine::Worker(JobsEngine* engine, + size_t workerIndex) + { + assert(engine != NULL); + + LOG(INFO) << "Worker thread " << workerIndex << " has started"; + + while (engine->IsRunning()) + { + JobsRegistry::RunningJob running(engine->GetRegistry(), engine->threadSleep_); + + if (running.IsValid()) + { + LOG(INFO) << "Executing job with priority " << running.GetPriority() + << " in worker thread " << workerIndex << ": " << running.GetId(); + + while (engine->IsRunning()) + { + if (!engine->ExecuteStep(running, workerIndex)) + { + break; + } + } + } + } + } + + + JobsEngine::JobsEngine() : + state_(State_Setup), + registry_(new JobsRegistry), + threadSleep_(200), + workers_(1) + { + } + + + JobsEngine::~JobsEngine() + { + if (state_ != State_Setup && + state_ != State_Done) + { + LOG(ERROR) << "INTERNAL ERROR: JobsEngine::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + + JobsRegistry& JobsEngine::GetRegistry() + { + if (registry_.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *registry_; + } + + + void JobsEngine::LoadRegistryFromJson(IJobUnserializer& unserializer, + const Json::Value& serialized) + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + // Can only be invoked before calling "Start()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + registry_.reset(new JobsRegistry(unserializer, serialized)); + } + + + void JobsEngine::LoadRegistryFromString(IJobUnserializer& unserializer, + const std::string& serialized) + { + Json::Value value; + Json::Reader reader; + if (reader.parse(serialized, value)) + { + LoadRegistryFromJson(unserializer, value); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void JobsEngine::SetWorkersCount(size_t count) + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + // Can only be invoked before calling "Start()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + workers_.resize(count); + } + + + void JobsEngine::SetThreadSleep(unsigned int sleep) + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + // Can only be invoked before calling "Start()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + threadSleep_ = sleep; + } + + + void JobsEngine::Start() + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Setup) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + retryHandler_ = boost::thread(RetryHandler, this); + + if (workers_.size() == 0) + { + // Use all the available CPUs + size_t n = boost::thread::hardware_concurrency(); + + if (n == 0) + { + n = 1; + } + + workers_.resize(n); + } + + for (size_t i = 0; i < workers_.size(); i++) + { + assert(workers_[i] == NULL); + workers_[i] = new boost::thread(Worker, this, i); + } + + state_ = State_Running; + + LOG(WARNING) << "The jobs engine has started with " << workers_.size() << " threads"; + } + + + void JobsEngine::Stop() + { + { + boost::mutex::scoped_lock lock(stateMutex_); + + if (state_ != State_Running) + { + return; + } + + state_ = State_Stopping; + } + + LOG(INFO) << "Stopping the jobs engine"; + + if (retryHandler_.joinable()) + { + retryHandler_.join(); + } + + for (size_t i = 0; i < workers_.size(); i++) + { + assert(workers_[i] != NULL); + + if (workers_[i]->joinable()) + { + workers_[i]->join(); + } + + delete workers_[i]; + } + + { + boost::mutex::scoped_lock lock(stateMutex_); + state_ = State_Done; + } + + LOG(WARNING) << "The jobs engine has stopped"; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsEngine.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,91 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobsRegistry.h" + +#include <boost/thread.hpp> + +namespace Orthanc +{ + class JobsEngine + { + private: + enum State + { + State_Setup, + State_Running, + State_Stopping, + State_Done + }; + + boost::mutex stateMutex_; + State state_; + std::auto_ptr<JobsRegistry> registry_; + boost::thread retryHandler_; + unsigned int threadSleep_; + std::vector<boost::thread*> workers_; + + bool IsRunning(); + + bool ExecuteStep(JobsRegistry::RunningJob& running, + size_t workerIndex); + + static void RetryHandler(JobsEngine* engine); + + static void Worker(JobsEngine* engine, + size_t workerIndex); + + public: + JobsEngine(); + + ~JobsEngine(); + + JobsRegistry& GetRegistry(); + + void LoadRegistryFromJson(IJobUnserializer& unserializer, + const Json::Value& serialized); + + void LoadRegistryFromString(IJobUnserializer& unserializer, + const std::string& serialized); + + void SetWorkersCount(size_t count); + + void SetThreadSleep(unsigned int sleep); + + void Start(); + + void Stop(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1335 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobsRegistry.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" +#include "../SerializationToolbox.h" + +namespace Orthanc +{ + static const char* STATE = "State"; + static const char* TYPE = "Type"; + static const char* PRIORITY = "Priority"; + static const char* JOB = "Job"; + static const char* JOBS = "Jobs"; + static const char* JOBS_REGISTRY = "JobsRegistry"; + static const char* MAX_COMPLETED_JOBS = "MaxCompletedJobs"; + static const char* CREATION_TIME = "CreationTime"; + static const char* LAST_CHANGE_TIME = "LastChangeTime"; + static const char* RUNTIME = "Runtime"; + + + class JobsRegistry::JobHandler : public boost::noncopyable + { + private: + std::string id_; + JobState state_; + std::string jobType_; + std::auto_ptr<IJob> job_; + int priority_; // "+inf()" means highest priority + boost::posix_time::ptime creationTime_; + boost::posix_time::ptime lastStateChangeTime_; + boost::posix_time::time_duration runtime_; + boost::posix_time::ptime retryTime_; + bool pauseScheduled_; + bool cancelScheduled_; + JobStatus lastStatus_; + + void Touch() + { + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + if (state_ == JobState_Running) + { + runtime_ += (now - lastStateChangeTime_); + } + + lastStateChangeTime_ = now; + } + + void SetStateInternal(JobState state) + { + state_ = state; + pauseScheduled_ = false; + cancelScheduled_ = false; + Touch(); + } + + public: + JobHandler(IJob* job, + int priority) : + id_(Toolbox::GenerateUuid()), + state_(JobState_Pending), + job_(job), + priority_(priority), + creationTime_(boost::posix_time::microsec_clock::universal_time()), + lastStateChangeTime_(creationTime_), + runtime_(boost::posix_time::milliseconds(0)), + retryTime_(creationTime_), + pauseScheduled_(false), + cancelScheduled_(false) + { + if (job == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + job->GetJobType(jobType_); + job->Start(); + + lastStatus_ = JobStatus(ErrorCode_Success, *job_); + } + + const std::string& GetId() const + { + return id_; + } + + IJob& GetJob() const + { + assert(job_.get() != NULL); + return *job_; + } + + void SetPriority(int priority) + { + priority_ = priority; + } + + int GetPriority() const + { + return priority_; + } + + JobState GetState() const + { + return state_; + } + + void SetState(JobState state) + { + if (state == JobState_Retry) + { + // Use "SetRetryState()" + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + SetStateInternal(state); + } + } + + void SetRetryState(unsigned int timeout) + { + if (state_ == JobState_Running) + { + SetStateInternal(JobState_Retry); + retryTime_ = (boost::posix_time::microsec_clock::universal_time() + + boost::posix_time::milliseconds(timeout)); + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + void SchedulePause() + { + if (state_ == JobState_Running) + { + pauseScheduled_ = true; + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + void ScheduleCancel() + { + if (state_ == JobState_Running) + { + cancelScheduled_ = true; + } + else + { + // Only valid for running jobs + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + bool IsPauseScheduled() + { + return pauseScheduled_; + } + + bool IsCancelScheduled() + { + return cancelScheduled_; + } + + bool IsRetryReady(const boost::posix_time::ptime& now) const + { + if (state_ != JobState_Retry) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return retryTime_ <= now; + } + } + + const boost::posix_time::ptime& GetCreationTime() const + { + return creationTime_; + } + + const boost::posix_time::ptime& GetLastStateChangeTime() const + { + return lastStateChangeTime_; + } + + void SetLastStateChangeTime(const boost::posix_time::ptime& time) + { + lastStateChangeTime_ = time; + } + + const boost::posix_time::time_duration& GetRuntime() const + { + return runtime_; + } + + const JobStatus& GetLastStatus() const + { + return lastStatus_; + } + + void SetLastStatus(const JobStatus& status) + { + lastStatus_ = status; + Touch(); + } + + void SetLastErrorCode(ErrorCode code) + { + lastStatus_.SetErrorCode(code); + } + + bool Serialize(Json::Value& target) const + { + target = Json::objectValue; + + bool ok; + + if (state_ == JobState_Running) + { + // WARNING: Cannot directly access the "job_" member, as long + // as a "RunningJob" instance is running. We do not use a + // mutex at the "JobHandler" level, as serialization would be + // blocked while a step in the job is running. Instead, we + // save a snapshot of the serialized job. + + if (lastStatus_.HasSerialized()) + { + target[JOB] = lastStatus_.GetSerialized(); + ok = true; + } + else + { + ok = false; + } + } + else + { + ok = job_->Serialize(target[JOB]); + } + + if (ok) + { + target[STATE] = EnumerationToString(state_); + target[PRIORITY] = priority_; + target[CREATION_TIME] = boost::posix_time::to_iso_string(creationTime_); + target[LAST_CHANGE_TIME] = boost::posix_time::to_iso_string(lastStateChangeTime_); + target[RUNTIME] = static_cast<unsigned int>(runtime_.total_milliseconds()); + return true; + } + else + { + LOG(INFO) << "Job backup is not supported for job of type: " << jobType_; + return false; + } + } + + JobHandler(IJobUnserializer& unserializer, + const Json::Value& serialized, + const std::string& id) : + id_(id), + pauseScheduled_(false), + cancelScheduled_(false) + { + state_ = StringToJobState(SerializationToolbox::ReadString(serialized, STATE)); + priority_ = SerializationToolbox::ReadInteger(serialized, PRIORITY); + creationTime_ = boost::posix_time::from_iso_string + (SerializationToolbox::ReadString(serialized, CREATION_TIME)); + lastStateChangeTime_ = boost::posix_time::from_iso_string + (SerializationToolbox::ReadString(serialized, LAST_CHANGE_TIME)); + runtime_ = boost::posix_time::milliseconds + (SerializationToolbox::ReadInteger(serialized, RUNTIME)); + + retryTime_ = creationTime_; + + job_.reset(unserializer.UnserializeJob(serialized[JOB])); + job_->GetJobType(jobType_); + job_->Start(); + + lastStatus_ = JobStatus(ErrorCode_Success, *job_); + } + }; + + + bool JobsRegistry::PriorityComparator::operator() (JobHandler*& a, + JobHandler*& b) const + { + return a->GetPriority() < b->GetPriority(); + } + + +#if defined(NDEBUG) + void JobsRegistry::CheckInvariants() const + { + } + +#else + bool JobsRegistry::IsPendingJob(const JobHandler& job) const + { + PendingJobs copy = pendingJobs_; + while (!copy.empty()) + { + if (copy.top() == &job) + { + return true; + } + + copy.pop(); + } + + return false; + } + + bool JobsRegistry::IsCompletedJob(JobHandler& job) const + { + for (CompletedJobs::const_iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + if (*it == &job) + { + return true; + } + } + + return false; + } + + bool JobsRegistry::IsRetryJob(JobHandler& job) const + { + return retryJobs_.find(&job) != retryJobs_.end(); + } + + void JobsRegistry::CheckInvariants() const + { + { + PendingJobs copy = pendingJobs_; + while (!copy.empty()) + { + assert(copy.top()->GetState() == JobState_Pending); + copy.pop(); + } + } + + assert(completedJobs_.size() <= maxCompletedJobs_); + + for (CompletedJobs::const_iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + assert((*it)->GetState() == JobState_Success || + (*it)->GetState() == JobState_Failure); + } + + for (RetryJobs::const_iterator it = retryJobs_.begin(); + it != retryJobs_.end(); ++it) + { + assert((*it)->GetState() == JobState_Retry); + } + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + JobHandler& job = *it->second; + + assert(job.GetId() == it->first); + + switch (job.GetState()) + { + case JobState_Pending: + assert(!IsRetryJob(job) && IsPendingJob(job) && !IsCompletedJob(job)); + break; + + case JobState_Success: + case JobState_Failure: + assert(!IsRetryJob(job) && !IsPendingJob(job) && IsCompletedJob(job)); + break; + + case JobState_Retry: + assert(IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); + break; + + case JobState_Running: + case JobState_Paused: + assert(!IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + } +#endif + + + void JobsRegistry::ForgetOldCompletedJobs() + { + if (maxCompletedJobs_ != 0) + { + while (completedJobs_.size() > maxCompletedJobs_) + { + assert(completedJobs_.front() != NULL); + + std::string id = completedJobs_.front()->GetId(); + assert(jobsIndex_.find(id) != jobsIndex_.end()); + + jobsIndex_.erase(id); + delete(completedJobs_.front()); + completedJobs_.pop_front(); + } + } + } + + + void JobsRegistry::SetCompletedJob(JobHandler& job, + bool success) + { + job.SetState(success ? JobState_Success : JobState_Failure); + + completedJobs_.push_back(&job); + ForgetOldCompletedJobs(); + + someJobComplete_.notify_all(); + } + + + void JobsRegistry::MarkRunningAsCompleted(JobHandler& job, + bool success) + { + LOG(INFO) << "Job has completed with " << (success ? "success" : "failure") + << ": " << job.GetId(); + + CheckInvariants(); + + assert(job.GetState() == JobState_Running); + SetCompletedJob(job, success); + + if (observer_ != NULL) + { + if (success) + { + observer_->SignalJobSuccess(job.GetId()); + } + else + { + observer_->SignalJobFailure(job.GetId()); + } + } + + CheckInvariants(); + } + + + void JobsRegistry::MarkRunningAsRetry(JobHandler& job, + unsigned int timeout) + { + LOG(INFO) << "Job scheduled for retry in " << timeout << "ms: " << job.GetId(); + + CheckInvariants(); + + assert(job.GetState() == JobState_Running && + retryJobs_.find(&job) == retryJobs_.end()); + + retryJobs_.insert(&job); + job.SetRetryState(timeout); + + CheckInvariants(); + } + + + void JobsRegistry::MarkRunningAsPaused(JobHandler& job) + { + LOG(INFO) << "Job paused: " << job.GetId(); + + CheckInvariants(); + assert(job.GetState() == JobState_Running); + + job.SetState(JobState_Paused); + + CheckInvariants(); + } + + + bool JobsRegistry::GetStateInternal(JobState& state, + const std::string& id) + { + CheckInvariants(); + + JobsIndex::const_iterator it = jobsIndex_.find(id); + if (it == jobsIndex_.end()) + { + return false; + } + else + { + state = it->second->GetState(); + return true; + } + } + + + JobsRegistry::~JobsRegistry() + { + for (JobsIndex::iterator it = jobsIndex_.begin(); it != jobsIndex_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + + void JobsRegistry::SetMaxCompletedJobs(size_t n) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + LOG(INFO) << "The size of the history of the jobs engine is set to: " << n << " job(s)"; + + maxCompletedJobs_ = n; + ForgetOldCompletedJobs(); + + CheckInvariants(); + } + + + void JobsRegistry::ListJobs(std::set<std::string>& target) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + target.insert(it->first); + } + } + + + bool JobsRegistry::GetJobInfo(JobInfo& target, + const std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::const_iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + return false; + } + else + { + const JobHandler& handler = *found->second; + target = JobInfo(handler.GetId(), + handler.GetPriority(), + handler.GetState(), + handler.GetLastStatus(), + handler.GetCreationTime(), + handler.GetLastStateChangeTime(), + handler.GetRuntime()); + return true; + } + } + + + void JobsRegistry::SubmitInternal(std::string& id, + JobHandler* handlerRaw, + bool keepLastChangeTime) + { + if (handlerRaw == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + std::auto_ptr<JobHandler> handler(handlerRaw); + + boost::posix_time::ptime lastChangeTime = handler->GetLastStateChangeTime(); + + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + id = handler->GetId(); + int priority = handler->GetPriority(); + + switch (handler->GetState()) + { + case JobState_Pending: + case JobState_Retry: + case JobState_Running: + handler->SetState(JobState_Pending); + pendingJobs_.push(handler.get()); + pendingJobAvailable_.notify_one(); + break; + + case JobState_Success: + SetCompletedJob(*handler, true); + break; + + case JobState_Failure: + SetCompletedJob(*handler, false); + break; + + case JobState_Paused: + break; + + default: + LOG(ERROR) << "A job should not be loaded from state: " + << EnumerationToString(handler->GetState()); + throw OrthancException(ErrorCode_InternalError); + } + + if (keepLastChangeTime) + { + handler->SetLastStateChangeTime(lastChangeTime); + } + + jobsIndex_.insert(std::make_pair(id, handler.release())); + + LOG(INFO) << "New job submitted with priority " << priority << ": " << id; + + if (observer_ != NULL) + { + observer_->SignalJobSubmitted(id); + } + + CheckInvariants(); + } + } + + + void JobsRegistry::Submit(std::string& id, + IJob* job, // Takes ownership + int priority) + { + SubmitInternal(id, new JobHandler(job, priority), false); + } + + + void JobsRegistry::Submit(IJob* job, // Takes ownership + int priority) + { + std::string id; + SubmitInternal(id, new JobHandler(job, priority), false); + } + + + bool JobsRegistry::SubmitAndWait(Json::Value& successContent, + IJob* job, // Takes ownership + int priority) + { + std::string id; + Submit(id, job, priority); + + JobState state = JobState_Pending; // Dummy initialization + + { + boost::mutex::scoped_lock lock(mutex_); + + for (;;) + { + if (!GetStateInternal(state, id)) + { + // Job has finished and has been lost (should not happen) + state = JobState_Failure; + break; + } + else if (state == JobState_Failure) + { + // Failure + break; + } + else if (state == JobState_Success) + { + // Success, try and retrieve the status of the job + JobsIndex::const_iterator it = jobsIndex_.find(id); + if (it == jobsIndex_.end()) + { + // Should not happen + state = JobState_Failure; + } + else + { + const JobStatus& status = it->second->GetLastStatus(); + successContent = status.GetPublicContent(); + } + + break; + } + else + { + // This job has not finished yet, wait for new completion + someJobComplete_.wait(lock); + } + } + } + + return (state == JobState_Success); + } + + + bool JobsRegistry::SetPriority(const std::string& id, + int priority) + { + LOG(INFO) << "Changing priority to " << priority << " for job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + found->second->SetPriority(priority); + + if (found->second->GetState() == JobState_Pending) + { + // If the job is pending, we need to reconstruct the + // priority queue, as the heap condition has changed + + PendingJobs copy; + std::swap(copy, pendingJobs_); + + assert(pendingJobs_.empty()); + while (!copy.empty()) + { + pendingJobs_.push(copy.top()); + copy.pop(); + } + } + + CheckInvariants(); + return true; + } + } + + + void JobsRegistry::RemovePendingJob(const std::string& id) + { + // If the job is pending, we need to reconstruct the priority + // queue to remove it + PendingJobs copy; + std::swap(copy, pendingJobs_); + + assert(pendingJobs_.empty()); + while (!copy.empty()) + { + if (copy.top()->GetId() != id) + { + pendingJobs_.push(copy.top()); + } + + copy.pop(); + } + } + + + void JobsRegistry::RemoveRetryJob(JobHandler* handler) + { + RetryJobs::iterator item = retryJobs_.find(handler); + assert(item != retryJobs_.end()); + retryJobs_.erase(item); + } + + + bool JobsRegistry::Pause(const std::string& id) + { + LOG(INFO) << "Pausing job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + switch (found->second->GetState()) + { + case JobState_Pending: + RemovePendingJob(id); + found->second->SetState(JobState_Paused); + break; + + case JobState_Retry: + RemoveRetryJob(found->second); + found->second->SetState(JobState_Paused); + break; + + case JobState_Paused: + case JobState_Success: + case JobState_Failure: + // Nothing to be done + break; + + case JobState_Running: + found->second->SchedulePause(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Cancel(const std::string& id) + { + LOG(INFO) << "Canceling job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else + { + switch (found->second->GetState()) + { + case JobState_Pending: + RemovePendingJob(id); + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Retry: + RemoveRetryJob(found->second); + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Paused: + SetCompletedJob(*found->second, false); + found->second->SetLastErrorCode(ErrorCode_CanceledJob); + break; + + case JobState_Success: + case JobState_Failure: + // Nothing to be done + break; + + case JobState_Running: + found->second->ScheduleCancel(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Resume(const std::string& id) + { + LOG(INFO) << "Resuming job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else if (found->second->GetState() != JobState_Paused) + { + LOG(WARNING) << "Cannot resume a job that is not paused: " << id; + return false; + } + else + { + found->second->SetState(JobState_Pending); + pendingJobs_.push(found->second); + pendingJobAvailable_.notify_one(); + CheckInvariants(); + return true; + } + } + + + bool JobsRegistry::Resubmit(const std::string& id) + { + LOG(INFO) << "Resubmitting failed job: " << id; + + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + JobsIndex::iterator found = jobsIndex_.find(id); + + if (found == jobsIndex_.end()) + { + LOG(WARNING) << "Unknown job: " << id; + return false; + } + else if (found->second->GetState() != JobState_Failure) + { + LOG(WARNING) << "Cannot resubmit a job that has not failed: " << id; + return false; + } + else + { + found->second->GetJob().Reset(); + + bool ok = false; + for (CompletedJobs::iterator it = completedJobs_.begin(); + it != completedJobs_.end(); ++it) + { + if (*it == found->second) + { + ok = true; + completedJobs_.erase(it); + break; + } + } + + assert(ok); + + found->second->SetState(JobState_Pending); + pendingJobs_.push(found->second); + pendingJobAvailable_.notify_one(); + + CheckInvariants(); + return true; + } + } + + + void JobsRegistry::ScheduleRetries() + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + RetryJobs copy; + std::swap(copy, retryJobs_); + + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + assert(retryJobs_.empty()); + for (RetryJobs::iterator it = copy.begin(); it != copy.end(); ++it) + { + if ((*it)->IsRetryReady(now)) + { + LOG(INFO) << "Retrying job: " << (*it)->GetId(); + (*it)->SetState(JobState_Pending); + pendingJobs_.push(*it); + pendingJobAvailable_.notify_one(); + } + else + { + retryJobs_.insert(*it); + } + } + + CheckInvariants(); + } + + + bool JobsRegistry::GetState(JobState& state, + const std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + return GetStateInternal(state, id); + } + + + void JobsRegistry::SetObserver(JobsRegistry::IObserver& observer) + { + boost::mutex::scoped_lock lock(mutex_); + observer_ = &observer; + } + + + void JobsRegistry::ResetObserver() + { + boost::mutex::scoped_lock lock(mutex_); + observer_ = NULL; + } + + + JobsRegistry::RunningJob::RunningJob(JobsRegistry& registry, + unsigned int timeout) : + registry_(registry), + handler_(NULL), + targetState_(JobState_Failure), + targetRetryTimeout_(0), + canceled_(false) + { + { + boost::mutex::scoped_lock lock(registry_.mutex_); + + while (registry_.pendingJobs_.empty()) + { + if (timeout == 0) + { + registry_.pendingJobAvailable_.wait(lock); + } + else + { + bool success = registry_.pendingJobAvailable_.timed_wait + (lock, boost::posix_time::milliseconds(timeout)); + if (!success) + { + // No pending job + return; + } + } + } + + handler_ = registry_.pendingJobs_.top(); + registry_.pendingJobs_.pop(); + + assert(handler_->GetState() == JobState_Pending); + handler_->SetState(JobState_Running); + handler_->SetLastErrorCode(ErrorCode_Success); + + job_ = &handler_->GetJob(); + id_ = handler_->GetId(); + priority_ = handler_->GetPriority(); + } + } + + + JobsRegistry::RunningJob::~RunningJob() + { + if (IsValid()) + { + boost::mutex::scoped_lock lock(registry_.mutex_); + + switch (targetState_) + { + case JobState_Failure: + registry_.MarkRunningAsCompleted(*handler_, false); + + if (canceled_) + { + handler_->SetLastErrorCode(ErrorCode_CanceledJob); + } + + break; + + case JobState_Success: + registry_.MarkRunningAsCompleted(*handler_, true); + break; + + case JobState_Paused: + registry_.MarkRunningAsPaused(*handler_); + break; + + case JobState_Retry: + registry_.MarkRunningAsRetry(*handler_, targetRetryTimeout_); + break; + + default: + assert(0); + } + } + } + + + bool JobsRegistry::RunningJob::IsValid() const + { + return (handler_ != NULL && + job_ != NULL); + } + + + const std::string& JobsRegistry::RunningJob::GetId() const + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return id_; + } + } + + + int JobsRegistry::RunningJob::GetPriority() const + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return priority_; + } + } + + + IJob& JobsRegistry::RunningJob::GetJob() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *job_; + } + } + + + bool JobsRegistry::RunningJob::IsPauseScheduled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + return handler_->IsPauseScheduled(); + } + } + + + bool JobsRegistry::RunningJob::IsCancelScheduled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + return handler_->IsCancelScheduled(); + } + } + + + void JobsRegistry::RunningJob::MarkSuccess() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Success; + } + } + + + void JobsRegistry::RunningJob::MarkFailure() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Failure; + } + } + + + void JobsRegistry::RunningJob::MarkCanceled() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Failure; + canceled_ = true; + } + } + + + void JobsRegistry::RunningJob::MarkPause() + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Paused; + } + } + + + void JobsRegistry::RunningJob::MarkRetry(unsigned int timeout) + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetState_ = JobState_Retry; + targetRetryTimeout_ = timeout; + } + } + + + void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code) + { + if (!IsValid()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + JobStatus status(code, *job_); + + boost::mutex::scoped_lock lock(registry_.mutex_); + registry_.CheckInvariants(); + assert(handler_->GetState() == JobState_Running); + + handler_->SetLastStatus(status); + } + } + + + + void JobsRegistry::Serialize(Json::Value& target) + { + boost::mutex::scoped_lock lock(mutex_); + CheckInvariants(); + + target = Json::objectValue; + target[TYPE] = JOBS_REGISTRY; + target[MAX_COMPLETED_JOBS] = static_cast<unsigned int>(maxCompletedJobs_); + target[JOBS] = Json::objectValue; + + for (JobsIndex::const_iterator it = jobsIndex_.begin(); + it != jobsIndex_.end(); ++it) + { + Json::Value v; + if (it->second->Serialize(v)) + { + target[JOBS][it->first] = v; + } + } + } + + + JobsRegistry::JobsRegistry(IJobUnserializer& unserializer, + const Json::Value& s) : + observer_(NULL) + { + if (SerializationToolbox::ReadString(s, TYPE) != JOBS_REGISTRY || + !s.isMember(JOBS) || + s[JOBS].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + maxCompletedJobs_ = SerializationToolbox::ReadUnsignedInteger(s, MAX_COMPLETED_JOBS); + + Json::Value::Members members = s[JOBS].getMemberNames(); + + for (Json::Value::Members::const_iterator it = members.begin(); + it != members.end(); ++it) + { + std::auto_ptr<JobHandler> job(new JobHandler(unserializer, s[JOBS][*it], *it)); + + std::string id; + SubmitInternal(id, job.release(), true); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/JobsRegistry.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,235 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The job engine cannot be used in sandboxed environments +#endif + +#include "JobInfo.h" +#include "IJobUnserializer.h" + +#include <list> +#include <set> +#include <queue> +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +namespace Orthanc +{ + // This class handles the state machine of the jobs engine + class JobsRegistry : public boost::noncopyable + { + public: + class IObserver : public boost::noncopyable + { + public: + virtual ~IObserver() + { + } + + virtual void SignalJobSubmitted(const std::string& jobId) = 0; + + virtual void SignalJobSuccess(const std::string& jobId) = 0; + + virtual void SignalJobFailure(const std::string& jobId) = 0; + }; + + private: + class JobHandler; + + struct PriorityComparator + { + bool operator() (JobHandler*& a, + JobHandler*& b) const; + }; + + typedef std::map<std::string, JobHandler*> JobsIndex; + typedef std::list<JobHandler*> CompletedJobs; + typedef std::set<JobHandler*> RetryJobs; + typedef std::priority_queue<JobHandler*, + std::vector<JobHandler*>, // Could be a "std::deque" + PriorityComparator> PendingJobs; + + boost::mutex mutex_; + JobsIndex jobsIndex_; + PendingJobs pendingJobs_; + CompletedJobs completedJobs_; + RetryJobs retryJobs_; + + boost::condition_variable pendingJobAvailable_; + boost::condition_variable someJobComplete_; + size_t maxCompletedJobs_; + + IObserver* observer_; + + +#ifndef NDEBUG + bool IsPendingJob(const JobHandler& job) const; + + bool IsCompletedJob(JobHandler& job) const; + + bool IsRetryJob(JobHandler& job) const; +#endif + + void CheckInvariants() const; + + void ForgetOldCompletedJobs(); + + void SetCompletedJob(JobHandler& job, + bool success); + + void MarkRunningAsCompleted(JobHandler& job, + bool success); + + void MarkRunningAsRetry(JobHandler& job, + unsigned int timeout); + + void MarkRunningAsPaused(JobHandler& job); + + bool GetStateInternal(JobState& state, + const std::string& id); + + void RemovePendingJob(const std::string& id); + + void RemoveRetryJob(JobHandler* handler); + + void SubmitInternal(std::string& id, + JobHandler* handler, + bool keepLastChangeTime); + + public: + JobsRegistry() : + maxCompletedJobs_(10), + observer_(NULL) + { + } + + JobsRegistry(IJobUnserializer& unserializer, + const Json::Value& s); + + ~JobsRegistry(); + + void SetMaxCompletedJobs(size_t i); + + void ListJobs(std::set<std::string>& target); + + bool GetJobInfo(JobInfo& target, + const std::string& id); + + void Serialize(Json::Value& target); + + void Submit(std::string& id, + IJob* job, // Takes ownership + int priority); + + void Submit(IJob* job, // Takes ownership + int priority); + + bool SubmitAndWait(Json::Value& successContent, + IJob* job, // Takes ownership + int priority); + + bool SetPriority(const std::string& id, + int priority); + + bool Pause(const std::string& id); + + bool Resume(const std::string& id); + + bool Resubmit(const std::string& id); + + bool Cancel(const std::string& id); + + void ScheduleRetries(); + + bool GetState(JobState& state, + const std::string& id); + + void SetObserver(IObserver& observer); + + void ResetObserver(); + + class RunningJob : public boost::noncopyable + { + private: + JobsRegistry& registry_; + JobHandler* handler_; // Can only be accessed if the + // registry mutex is locked! + IJob* job_; // Will by design be in mutual exclusion, + // because only one RunningJob can be + // executed at a time on a JobHandler + + std::string id_; + int priority_; + JobState targetState_; + unsigned int targetRetryTimeout_; + bool canceled_; + + public: + RunningJob(JobsRegistry& registry, + unsigned int timeout); + + ~RunningJob(); + + bool IsValid() const; + + const std::string& GetId() const; + + int GetPriority() const; + + IJob& GetJob(); + + bool IsPauseScheduled(); + + bool IsCancelScheduled(); + + void MarkSuccess(); + + void MarkFailure(); + + void MarkPause(); + + void MarkCanceled(); + + void MarkRetry(unsigned int timeout); + + void UpdateStatus(ErrorCode code); + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/IJobOperation.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobOperationValues.h" +#include "../../DicomNetworking/IDicomConnectionManager.h" + +namespace Orthanc +{ + class IJobOperation : public boost::noncopyable + { + public: + virtual ~IJobOperation() + { + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& dicomConnection) = 0; + + virtual void Serialize(Json::Value& result) const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValue.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,74 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 <json/value.h> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class JobOperationValue : public boost::noncopyable + { + public: + enum Type + { + Type_DicomInstance, + Type_Null, + Type_String + }; + + private: + Type type_; + + protected: + JobOperationValue(Type type) : + type_(type) + { + } + + public: + virtual ~JobOperationValue() + { + } + + Type GetType() const + { + return type_; + } + + virtual JobOperationValue* Clone() const = 0; + + virtual void Serialize(Json::Value& target) const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,143 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobOperationValues.h" + +#include "../IJobUnserializer.h" +#include "../../OrthancException.h" + +#include <cassert> +#include <memory> + +namespace Orthanc +{ + void JobOperationValues::Append(JobOperationValues& target, + bool clear) + { + target.Reserve(target.GetSize() + GetSize()); + + for (size_t i = 0; i < values_.size(); i++) + { + if (clear) + { + target.Append(values_[i]); + values_[i] = NULL; + } + else + { + target.Append(GetValue(i).Clone()); + } + } + + if (clear) + { + Clear(); + } + } + + + void JobOperationValues::Clear() + { + for (size_t i = 0; i < values_.size(); i++) + { + if (values_[i] != NULL) + { + delete values_[i]; + } + } + + values_.clear(); + } + + + void JobOperationValues::Append(JobOperationValue* value) // Takes ownership + { + if (value == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + values_.push_back(value); + } + } + + + JobOperationValue& JobOperationValues::GetValue(size_t index) const + { + if (index >= values_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + assert(values_[index] != NULL); + return *values_[index]; + } + } + + + void JobOperationValues::Serialize(Json::Value& target) const + { + target = Json::arrayValue; + + for (size_t i = 0; i < values_.size(); i++) + { + Json::Value tmp; + values_[i]->Serialize(tmp); + target.append(tmp); + } + } + + + JobOperationValues* JobOperationValues::Unserialize(IJobUnserializer& unserializer, + const Json::Value& source) + { + if (source.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::auto_ptr<JobOperationValues> result(new JobOperationValues); + + result->Reserve(source.size()); + + for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) + { + result->Append(unserializer.UnserializeValue(source[i])); + } + + return result.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/JobOperationValues.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobOperationValue.h" + +#include <vector> + +namespace Orthanc +{ + class IJobUnserializer; + + class JobOperationValues : public boost::noncopyable + { + private: + std::vector<JobOperationValue*> values_; + + void Append(JobOperationValues& target, + bool clear); + + public: + ~JobOperationValues() + { + Clear(); + } + + void Move(JobOperationValues& target) + { + return Append(target, true); + } + + void Copy(JobOperationValues& target) + { + return Append(target, false); + } + + void Clear(); + + void Reserve(size_t count) + { + values_.reserve(count); + } + + void Append(JobOperationValue* value); // Takes ownership + + size_t GetSize() const + { + return values_.size(); + } + + JobOperationValue& GetValue(size_t index) const; + + void Serialize(Json::Value& target) const; + + static JobOperationValues* Unserialize(IJobUnserializer& unserializer, + const Json::Value& source); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "LogJobOperation.h" + +#include "../../Logging.h" +#include "StringOperationValue.h" + +namespace Orthanc +{ + void LogJobOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + switch (input.GetType()) + { + case JobOperationValue::Type_String: + LOG(INFO) << "Job value: " + << dynamic_cast<const StringOperationValue&>(input).GetContent(); + break; + + case JobOperationValue::Type_Null: + LOG(INFO) << "Job value: (null)"; + break; + + default: + LOG(INFO) << "Job value: (unsupport)"; + break; + } + + outputs.Append(input.Clone()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/LogJobOperation.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IJobOperation.h" + +namespace Orthanc +{ + class LogJobOperation : public IJobOperation + { + public: + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "Log"; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/NullOperationValue.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,59 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobOperationValue.h" + +namespace Orthanc +{ + class NullOperationValue : public JobOperationValue + { + public: + NullOperationValue() : + JobOperationValue(Type_Null) + { + } + + virtual JobOperationValue* Clone() const + { + return new NullOperationValue; + } + + virtual void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "Null"; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,495 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SequenceOfOperationsJob.h" + +#include "../../Logging.h" +#include "../../OrthancException.h" +#include "../../SerializationToolbox.h" +#include "../IJobUnserializer.h" + +namespace Orthanc +{ + static const char* CURRENT = "Current"; + static const char* DESCRIPTION = "Description"; + static const char* DICOM_TIMEOUT = "DicomTimeout"; + static const char* NEXT_OPERATIONS = "Next"; + static const char* OPERATION = "Operation"; + static const char* OPERATIONS = "Operations"; + static const char* ORIGINAL_INPUTS = "OriginalInputs"; + static const char* TRAILING_TIMEOUT = "TrailingTimeout"; + static const char* TYPE = "Type"; + static const char* WORK_INPUTS = "WorkInputs"; + + + 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_; + + public: + Operation(size_t index, + IJobOperation* operation) : + index_(index), + operation_(operation), + originalInputs_(new JobOperationValues), + workInputs_(new JobOperationValues), + currentInput_(0) + { + if (operation == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + void AddOriginalInput(const JobOperationValue& value) + { + if (currentInput_ != 0) + { + // Cannot add input after processing has started + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + originalInputs_->Append(value.Clone()); + } + } + + const JobOperationValues& GetOriginalInputs() const + { + return *originalInputs_; + } + + void Reset() + { + workInputs_->Clear(); + currentInput_ = 0; + } + + void AddNextOperation(Operation& other, + bool unserializing) + { + if (other.index_ <= index_) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (!unserializing && + currentInput_ != 0) + { + // Cannot add input after processing has started + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + nextOperations_.push_back(&other); + } + } + + bool IsDone() const + { + return currentInput_ >= originalInputs_->GetSize() + workInputs_->GetSize(); + } + + void Step(IDicomConnectionManager& connectionManager) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + const JobOperationValue* input; + + if (currentInput_ < originalInputs_->GetSize()) + { + input = &originalInputs_->GetValue(currentInput_); + } + else + { + input = &workInputs_->GetValue(currentInput_ - originalInputs_->GetSize()); + } + + JobOperationValues outputs; + operation_->Apply(outputs, *input, connectionManager); + + if (!nextOperations_.empty()) + { + std::list<Operation*>::iterator first = nextOperations_.begin(); + outputs.Move(*(*first)->workInputs_); + + std::list<Operation*>::iterator current = first; + ++current; + + while (current != nextOperations_.end()) + { + (*first)->workInputs_->Copy(*(*current)->workInputs_); + ++current; + } + } + + currentInput_ += 1; + } + + void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target[CURRENT] = static_cast<unsigned int>(currentInput_); + operation_->Serialize(target[OPERATION]); + originalInputs_->Serialize(target[ORIGINAL_INPUTS]); + workInputs_->Serialize(target[WORK_INPUTS]); + + Json::Value tmp = Json::arrayValue; + for (std::list<Operation*>::const_iterator it = nextOperations_.begin(); + it != nextOperations_.end(); ++it) + { + tmp.append(static_cast<int>((*it)->index_)); + } + + target[NEXT_OPERATIONS] = tmp; + } + + Operation(IJobUnserializer& unserializer, + Json::Value::ArrayIndex index, + const Json::Value& serialized) : + index_(index) + { + if (serialized.type() != Json::objectValue || + !serialized.isMember(OPERATION) || + !serialized.isMember(ORIGINAL_INPUTS) || + !serialized.isMember(WORK_INPUTS)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + currentInput_ = SerializationToolbox::ReadUnsignedInteger(serialized, CURRENT); + operation_.reset(unserializer.UnserializeOperation(serialized[OPERATION])); + originalInputs_.reset(JobOperationValues::Unserialize + (unserializer, serialized[ORIGINAL_INPUTS])); + workInputs_.reset(JobOperationValues::Unserialize + (unserializer, serialized[WORK_INPUTS])); + } + }; + + + SequenceOfOperationsJob::SequenceOfOperationsJob() : + done_(false), + current_(0), + trailingTimeout_(boost::posix_time::milliseconds(1000)) + { + } + + + SequenceOfOperationsJob::~SequenceOfOperationsJob() + { + for (size_t i = 0; i < operations_.size(); i++) + { + if (operations_[i] != NULL) + { + delete operations_[i]; + } + } + } + + + void SequenceOfOperationsJob::SetDescription(const std::string& description) + { + boost::mutex::scoped_lock lock(mutex_); + description_ = description; + } + + + void SequenceOfOperationsJob::GetDescription(std::string& description) + { + boost::mutex::scoped_lock lock(mutex_); + description = description_; + } + + + void SequenceOfOperationsJob::Register(IObserver& observer) + { + boost::mutex::scoped_lock lock(mutex_); + observers_.push_back(&observer); + } + + + void SequenceOfOperationsJob::Lock::SetTrailingOperationTimeout(unsigned int timeout) + { + that_.trailingTimeout_ = boost::posix_time::milliseconds(timeout); + } + + + void SequenceOfOperationsJob::Lock::SetDicomAssociationTimeout(unsigned int timeout) + { + that_.connectionManager_.SetTimeout(timeout); + } + + + size_t SequenceOfOperationsJob::Lock::AddOperation(IJobOperation* operation) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + size_t index = that_.operations_.size(); + + that_.operations_.push_back(new Operation(index, operation)); + that_.operationAdded_.notify_one(); + + return index; + } + + + void SequenceOfOperationsJob::Lock::AddInput(size_t index, + const JobOperationValue& value) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (index >= that_.operations_.size() || + index < that_.current_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + that_.operations_[index]->AddOriginalInput(value); + } + } + + + void SequenceOfOperationsJob::Lock::Connect(size_t input, + size_t output) + { + if (IsDone()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (input >= output || + input >= that_.operations_.size() || + output >= that_.operations_.size() || + input < that_.current_ || + output < that_.current_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + Operation& a = *that_.operations_[input]; + Operation& b = *that_.operations_[output]; + a.AddNextOperation(b, false /* not unserializing */); + } + } + + + JobStepResult SequenceOfOperationsJob::Step() + { + boost::mutex::scoped_lock lock(mutex_); + + if (current_ == operations_.size()) + { + LOG(INFO) << "Executing the trailing timeout in the sequence of operations"; + operationAdded_.timed_wait(lock, trailingTimeout_); + + if (current_ == operations_.size()) + { + // No operation was added during the trailing timeout: The + // job is over + LOG(INFO) << "The sequence of operations is over"; + done_ = true; + + for (std::list<IObserver*>::iterator it = observers_.begin(); + it != observers_.end(); ++it) + { + (*it)->SignalDone(*this); + } + + connectionManager_.Close(); + return JobStepResult::Success(); + } + else + { + LOG(INFO) << "New operation were added to the sequence of operations"; + } + } + + assert(current_ < operations_.size()); + + while (current_ < operations_.size() && + operations_[current_]->IsDone()) + { + current_++; + } + + if (current_ < operations_.size()) + { + operations_[current_]->Step(connectionManager_); + } + + connectionManager_.CheckTimeout(); + + return JobStepResult::Continue(); + } + + + void SequenceOfOperationsJob::Reset() + { + boost::mutex::scoped_lock lock(mutex_); + + current_ = 0; + done_ = false; + + for (size_t i = 0; i < operations_.size(); i++) + { + operations_[i]->Reset(); + } + } + + + void SequenceOfOperationsJob::Stop(JobStopReason reason) + { + boost::mutex::scoped_lock lock(mutex_); + connectionManager_.Close(); + } + + + float SequenceOfOperationsJob::GetProgress() + { + boost::mutex::scoped_lock lock(mutex_); + + return (static_cast<float>(current_) / + static_cast<float>(operations_.size() + 1)); + } + + + void SequenceOfOperationsJob::GetPublicContent(Json::Value& value) + { + boost::mutex::scoped_lock lock(mutex_); + + value["CountOperations"] = static_cast<unsigned int>(operations_.size()); + value["Description"] = description_; + } + + + bool SequenceOfOperationsJob::Serialize(Json::Value& value) + { + boost::mutex::scoped_lock lock(mutex_); + + value = Json::objectValue; + + std::string jobType; + GetJobType(jobType); + value[TYPE] = jobType; + + value[DESCRIPTION] = description_; + value[TRAILING_TIMEOUT] = static_cast<unsigned int>(trailingTimeout_.total_milliseconds()); + value[DICOM_TIMEOUT] = connectionManager_.GetTimeout(); + value[CURRENT] = static_cast<unsigned int>(current_); + + Json::Value tmp = Json::arrayValue; + for (size_t i = 0; i < operations_.size(); i++) + { + Json::Value operation = Json::objectValue; + operations_[i]->Serialize(operation); + tmp.append(operation); + } + + value[OPERATIONS] = tmp; + + return true; + } + + + SequenceOfOperationsJob::SequenceOfOperationsJob(IJobUnserializer& unserializer, + const Json::Value& serialized) : + done_(false) + { + std::string jobType; + GetJobType(jobType); + + if (SerializationToolbox::ReadString(serialized, TYPE) != jobType || + !serialized.isMember(OPERATIONS) || + serialized[OPERATIONS].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + description_ = SerializationToolbox::ReadString(serialized, DESCRIPTION); + trailingTimeout_ = boost::posix_time::milliseconds + (SerializationToolbox::ReadUnsignedInteger(serialized, TRAILING_TIMEOUT)); + connectionManager_.SetTimeout + (SerializationToolbox::ReadUnsignedInteger(serialized, DICOM_TIMEOUT)); + current_ = SerializationToolbox::ReadUnsignedInteger(serialized, CURRENT); + + const Json::Value& ops = serialized[OPERATIONS]; + + // Unserialize the individual operations + operations_.reserve(ops.size()); + for (Json::Value::ArrayIndex i = 0; i < ops.size(); i++) + { + operations_.push_back(new Operation(unserializer, i, ops[i])); + } + + // Connect the next operations + for (Json::Value::ArrayIndex i = 0; i < ops.size(); i++) + { + if (!ops[i].isMember(NEXT_OPERATIONS) || + ops[i][NEXT_OPERATIONS].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& next = ops[i][NEXT_OPERATIONS]; + for (Json::Value::ArrayIndex j = 0; j < next.size(); j++) + { + if (next[j].type() != Json::intValue || + next[j].asInt() < 0 || + next[j].asUInt() >= operations_.size()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + operations_[i]->AddNextOperation(*operations_[next[j].asUInt()], true); + } + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,153 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../IJob.h" +#include "IJobOperation.h" + +#include "../../DicomNetworking/TimeoutDicomConnectionManager.h" + +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +#include <list> + +namespace Orthanc +{ + class SequenceOfOperationsJob : public IJob + { + public: + class IObserver : public boost::noncopyable + { + public: + virtual ~IObserver() + { + } + + virtual void SignalDone(const SequenceOfOperationsJob& job) = 0; + }; + + private: + class Operation; + + std::string description_; + bool done_; + boost::mutex mutex_; + std::vector<Operation*> operations_; + size_t current_; + boost::condition_variable operationAdded_; + boost::posix_time::time_duration trailingTimeout_; + std::list<IObserver*> observers_; + TimeoutDicomConnectionManager connectionManager_; + + public: + SequenceOfOperationsJob(); + + SequenceOfOperationsJob(IJobUnserializer& unserializer, + const Json::Value& serialized); + + virtual ~SequenceOfOperationsJob(); + + void SetDescription(const std::string& description); + + void GetDescription(std::string& description); + + void Register(IObserver& observer); + + // This lock allows adding new operations to the end of the job, + // from another thread than the worker thread, after the job has + // been submitted for processing + class Lock : public boost::noncopyable + { + private: + SequenceOfOperationsJob& that_; + boost::mutex::scoped_lock lock_; + + public: + Lock(SequenceOfOperationsJob& that) : + that_(that), + lock_(that.mutex_) + { + } + + bool IsDone() const + { + return that_.done_; + } + + void SetTrailingOperationTimeout(unsigned int timeout); + + void SetDicomAssociationTimeout(unsigned int timeout); + + size_t AddOperation(IJobOperation* operation); + + size_t GetOperationsCount() const + { + return that_.operations_.size(); + } + + void AddInput(size_t index, + const JobOperationValue& value); + + void Connect(size_t input, + size_t output); + }; + + virtual void Start() + { + } + + virtual JobStepResult Step(); + + virtual void Reset(); + + virtual void Stop(JobStopReason reason); + + virtual float GetProgress(); + + virtual void GetJobType(std::string& target) + { + target = "SequenceOfOperations"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value); + + void AwakeTrailingSleep() + { + operationAdded_.notify_one(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/Operations/StringOperationValue.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,71 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "JobOperationValue.h" + +#include <string> + +namespace Orthanc +{ + class StringOperationValue : public JobOperationValue + { + private: + std::string content_; + + public: + StringOperationValue(const std::string& content) : + JobOperationValue(JobOperationValue::Type_String), + content_(content) + { + } + + virtual JobOperationValue* Clone() const + { + return new StringOperationValue(content_); + } + + const std::string& GetContent() const + { + return content_; + } + + virtual void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "String"; + target["Content"] = content_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfCommandsJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,303 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SetOfCommandsJob.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../SerializationToolbox.h" + +#include <cassert> +#include <memory> + +namespace Orthanc +{ + SetOfCommandsJob::SetOfCommandsJob() : + started_(false), + permissive_(false), + position_(0) + { + } + + + SetOfCommandsJob::~SetOfCommandsJob() + { + for (size_t i = 0; i < commands_.size(); i++) + { + assert(commands_[i] != NULL); + delete commands_[i]; + } + } + + + void SetOfCommandsJob::Reserve(size_t size) + { + if (started_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + commands_.reserve(size); + } + } + + + void SetOfCommandsJob::AddCommand(ICommand* command) + { + if (command == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else if (started_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + commands_.push_back(command); + } + } + + + void SetOfCommandsJob::SetPermissive(bool permissive) + { + if (started_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + permissive_ = permissive; + } + } + + + void SetOfCommandsJob::Reset() + { + if (started_) + { + position_ = 0; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + float SetOfCommandsJob::GetProgress() + { + if (commands_.empty()) + { + return 1; + } + else + { + return (static_cast<float>(position_) / + static_cast<float>(commands_.size())); + } + } + + + const SetOfCommandsJob::ICommand& SetOfCommandsJob::GetCommand(size_t index) const + { + if (index >= commands_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + assert(commands_[index] != NULL); + return *commands_[index]; + } + } + + + JobStepResult SetOfCommandsJob::Step() + { + if (!started_) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (commands_.empty() && + position_ == 0) + { + // No command to handle: We're done + position_ = 1; + return JobStepResult::Success(); + } + + if (position_ >= commands_.size()) + { + // Already done + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + try + { + // Not at the trailing step: Handle the current command + if (!commands_[position_]->Execute()) + { + // Error + if (!permissive_) + { + return JobStepResult::Failure(ErrorCode_InternalError); + } + } + } + catch (OrthancException& e) + { + if (permissive_) + { + LOG(WARNING) << "Ignoring an error in a permissive job: " << e.What(); + } + else + { + return JobStepResult::Failure(e.GetErrorCode()); + } + } + + position_ += 1; + + if (position_ == commands_.size()) + { + // We're done + return JobStepResult::Success(); + } + else + { + return JobStepResult::Continue(); + } + } + + + + static const char* KEY_DESCRIPTION = "Description"; + static const char* KEY_PERMISSIVE = "Permissive"; + static const char* KEY_POSITION = "Position"; + static const char* KEY_TYPE = "Type"; + static const char* KEY_COMMANDS = "Commands"; + + + void SetOfCommandsJob::GetPublicContent(Json::Value& value) + { + value[KEY_DESCRIPTION] = GetDescription(); + } + + + bool SetOfCommandsJob::Serialize(Json::Value& target) + { + target = Json::objectValue; + + std::string type; + GetJobType(type); + target[KEY_TYPE] = type; + + target[KEY_PERMISSIVE] = permissive_; + target[KEY_POSITION] = static_cast<unsigned int>(position_); + target[KEY_DESCRIPTION] = description_; + + target[KEY_COMMANDS] = Json::arrayValue; + Json::Value& tmp = target[KEY_COMMANDS]; + + for (size_t i = 0; i < commands_.size(); i++) + { + assert(commands_[i] != NULL); + + Json::Value command; + commands_[i]->Serialize(command); + tmp.append(command); + } + + return true; + } + + + SetOfCommandsJob::SetOfCommandsJob(ICommandUnserializer* unserializer, + const Json::Value& source) : + started_(false) + { + std::auto_ptr<ICommandUnserializer> raii(unserializer); + + permissive_ = SerializationToolbox::ReadBoolean(source, KEY_PERMISSIVE); + position_ = SerializationToolbox::ReadUnsignedInteger(source, KEY_POSITION); + description_ = SerializationToolbox::ReadString(source, KEY_DESCRIPTION); + + if (!source.isMember(KEY_COMMANDS) || + source[KEY_COMMANDS].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + const Json::Value& tmp = source[KEY_COMMANDS]; + commands_.resize(tmp.size()); + + for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++) + { + try + { + commands_[i] = unserializer->Unserialize(tmp[i]); + } + catch (OrthancException&) + { + } + + if (commands_[i] == NULL) + { + for (size_t j = 0; j < i; j++) + { + delete commands_[j]; + } + + throw OrthancException(ErrorCode_BadFileFormat); + } + } + } + + if (commands_.empty()) + { + if (position_ > 1) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + else if (position_ > commands_.size()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfCommandsJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,135 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IJob.h" + +#include <set> + +namespace Orthanc +{ + class SetOfCommandsJob : public IJob + { + public: + class ICommand : public boost::noncopyable + { + public: + virtual ~ICommand() + { + } + + virtual bool Execute() = 0; + + virtual void Serialize(Json::Value& target) const = 0; + }; + + class ICommandUnserializer : public boost::noncopyable + { + public: + virtual ~ICommandUnserializer() + { + } + + virtual ICommand* Unserialize(const Json::Value& source) const = 0; + }; + + private: + bool started_; + std::vector<ICommand*> commands_; + bool permissive_; + size_t position_; + std::string description_; + + public: + SetOfCommandsJob(); + + SetOfCommandsJob(ICommandUnserializer* unserializer /* takes ownership */, + const Json::Value& source); + + virtual ~SetOfCommandsJob(); + + size_t GetPosition() const + { + return position_; + } + + void SetDescription(const std::string& description) + { + description_ = description; + } + + const std::string& GetDescription() const + { + return description_; + } + + void Reserve(size_t size); + + size_t GetCommandsCount() const + { + return commands_.size(); + } + + void AddCommand(ICommand* command); // Takes ownership + + bool IsPermissive() const + { + return permissive_; + } + + void SetPermissive(bool permissive); + + virtual void Reset(); + + virtual void Start() + { + started_ = true; + } + + virtual float GetProgress(); + + bool IsStarted() const + { + return started_; + } + + const ICommand& GetCommand(size_t index) const; + + virtual JobStepResult Step(); + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,240 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SetOfInstancesJob.h" + +#include "../OrthancException.h" +#include "../SerializationToolbox.h" + +#include <cassert> + +namespace Orthanc +{ + class SetOfInstancesJob::InstanceCommand : public SetOfInstancesJob::ICommand + { + private: + SetOfInstancesJob& that_; + std::string instance_; + + public: + InstanceCommand(SetOfInstancesJob& that, + const std::string& instance) : + that_(that), + instance_(instance) + { + } + + const std::string& GetInstance() const + { + return instance_; + } + + virtual bool Execute() + { + if (!that_.HandleInstance(instance_)) + { + that_.failedInstances_.insert(instance_); + return false; + } + else + { + return true; + } + } + + virtual void Serialize(Json::Value& target) const + { + target = instance_; + } + }; + + + class SetOfInstancesJob::TrailingStepCommand : public SetOfInstancesJob::ICommand + { + private: + SetOfInstancesJob& that_; + + public: + TrailingStepCommand(SetOfInstancesJob& that) : + that_(that) + { + } + + virtual bool Execute() + { + return that_.HandleTrailingStep(); + } + + virtual void Serialize(Json::Value& target) const + { + target = Json::nullValue; + } + }; + + + class SetOfInstancesJob::InstanceUnserializer : + public SetOfInstancesJob::ICommandUnserializer + { + private: + SetOfInstancesJob& that_; + + public: + InstanceUnserializer(SetOfInstancesJob& that) : + that_(that) + { + } + + virtual ICommand* Unserialize(const Json::Value& source) const + { + if (source.type() == Json::nullValue) + { + return new TrailingStepCommand(that_); + } + else if (source.type() == Json::stringValue) + { + return new InstanceCommand(that_, source.asString()); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + }; + + + SetOfInstancesJob::SetOfInstancesJob() : + hasTrailingStep_(false) + { + } + + + void SetOfInstancesJob::AddInstance(const std::string& instance) + { + AddCommand(new InstanceCommand(*this, instance)); + } + + + void SetOfInstancesJob::AddTrailingStep() + { + AddCommand(new TrailingStepCommand(*this)); + hasTrailingStep_ = true; + } + + + size_t SetOfInstancesJob::GetInstancesCount() const + { + if (hasTrailingStep_) + { + assert(GetCommandsCount() > 0); + return GetCommandsCount() - 1; + } + else + { + return GetCommandsCount(); + } + } + + + const std::string& SetOfInstancesJob::GetInstance(size_t index) const + { + if (index >= GetInstancesCount()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return dynamic_cast<const InstanceCommand&>(GetCommand(index)).GetInstance(); + } + } + + + void SetOfInstancesJob::Start() + { + SetOfCommandsJob::Start(); + } + + + void SetOfInstancesJob::Reset() + { + SetOfCommandsJob::Reset(); + + failedInstances_.clear(); + } + + + void SetOfInstancesJob::GetPublicContent(Json::Value& target) + { + SetOfCommandsJob::GetPublicContent(target); + target["InstancesCount"] = static_cast<uint32_t>(GetInstancesCount()); + target["FailedInstancesCount"] = static_cast<uint32_t>(failedInstances_.size()); + } + + + + static const char* KEY_TRAILING_STEP = "TrailingStep"; + static const char* KEY_FAILED_INSTANCES = "FailedInstances"; + + bool SetOfInstancesJob::Serialize(Json::Value& target) + { + if (SetOfCommandsJob::Serialize(target)) + { + target[KEY_TRAILING_STEP] = hasTrailingStep_; + SerializationToolbox::WriteSetOfStrings(target, failedInstances_, KEY_FAILED_INSTANCES); + return true; + } + else + { + return false; + } + } + + + SetOfInstancesJob::SetOfInstancesJob(const Json::Value& source) : + SetOfCommandsJob(new InstanceUnserializer(*this), source) + { + SerializationToolbox::ReadSetOfStrings(failedInstances_, source, KEY_FAILED_INSTANCES); + + if (source.isMember(KEY_TRAILING_STEP)) + { + hasTrailingStep_ = SerializationToolbox::ReadBoolean(source, KEY_TRAILING_STEP); + } + else + { + // Backward compatibility with Orthanc <= 1.4.2 + hasTrailingStep_ = false; + } + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/JobsEngine/SetOfInstancesJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,97 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IJob.h" +#include "SetOfCommandsJob.h" + +#include <set> + +namespace Orthanc +{ + class SetOfInstancesJob : public SetOfCommandsJob + { + private: + class InstanceCommand; + class TrailingStepCommand; + class InstanceUnserializer; + + bool hasTrailingStep_; + std::set<std::string> failedInstances_; + + protected: + virtual bool HandleInstance(const std::string& instance) = 0; + + virtual bool HandleTrailingStep() = 0; + + // Hiding this method, use AddInstance() instead + using SetOfCommandsJob::AddCommand; + + public: + SetOfInstancesJob(); + + SetOfInstancesJob(const Json::Value& source); // Unserialization + + void AddInstance(const std::string& instance); + + void AddTrailingStep(); + + size_t GetInstancesCount() const; + + const std::string& GetInstance(size_t index) const; + + bool HasTrailingStep() const + { + return hasTrailingStep_; + } + + const std::set<std::string>& GetFailedInstances() const + { + return failedInstances_; + } + + bool IsFailedInstance(const std::string& instance) const + { + return failedInstances_.find(instance) != failedInstances_.end(); + } + + virtual void Start(); + + virtual void Reset(); + + virtual void GetPublicContent(Json::Value& target); + + virtual bool Serialize(Json::Value& target); + }; +}
--- a/Core/Logging.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Logging.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +48,14 @@ { } + void Reset() + { + } + + void Flush() + { + } + void EnableInfoLevel(bool enabled) { } @@ -55,90 +64,179 @@ { } + void SetTargetFile(const std::string& path) + { + } + void SetTargetFolder(const std::string& path) { } } } -#elif ORTHANC_ENABLE_GOOGLE_LOG == 1 + +#elif ORTHANC_ENABLE_LOGGING_PLUGIN == 1 /********************************************************* - * Wrapper around Google Log + * Logger compatible with the Orthanc plugin SDK *********************************************************/ +#include <boost/lexical_cast.hpp> + namespace Orthanc -{ +{ namespace Logging { - void Initialize() + static OrthancPluginContext* context_ = NULL; + + void Initialize(OrthancPluginContext* context) { - // Initialize Google's logging library. - FLAGS_logtostderr = true; - FLAGS_minloglevel = 1; // Do not print LOG(INFO) by default - FLAGS_v = 0; // Do not print trace-level VLOG(1) by default + context_ = context; + } - google::InitGoogleLogging("Orthanc"); + InternalLogger::InternalLogger(InternalLevel level, + const char* file /* ignored */, + int line /* ignored */) : + level_(level) + { } - void Finalize() + InternalLogger::~InternalLogger() { - google::ShutdownGoogleLogging(); + if (context_ != NULL) + { + switch (level_) + { + case InternalLevel_ERROR: + OrthancPluginLogError(context_, message_.c_str()); + break; + + case InternalLevel_WARNING: + OrthancPluginLogWarning(context_, message_.c_str()); + break; + + case InternalLevel_INFO: + OrthancPluginLogInfo(context_, message_.c_str()); + break; + + case InternalLevel_TRACE: + // Not used by plugins + break; + + default: + { + std::string s = ("Unknown log level (" + boost::lexical_cast<std::string>(level_) + + ") for message: " + message_); + OrthancPluginLogError(context_, s.c_str()); + break; + } + } + } + } + } +} + + +#elif ORTHANC_ENABLE_LOGGING_STDIO == 1 + +/********************************************************* + * Logger compatible with <stdio.h> + *********************************************************/ + +#include <stdio.h> +#include <boost/lexical_cast.hpp> + +namespace Orthanc +{ + namespace Logging + { + static bool globalVerbose_ = false; + static bool globalTrace_ = false; + + InternalLogger::InternalLogger(InternalLevel level, + const char* file /* ignored */, + int line /* ignored */) : + level_(level) + { + } + + InternalLogger::~InternalLogger() + { + switch (level_) + { + case InternalLevel_ERROR: + fprintf(stderr, "E: %s\n", message_.c_str()); + break; + + case InternalLevel_WARNING: + fprintf(stdout, "W: %s\n", message_.c_str()); + break; + + case InternalLevel_INFO: + if (globalVerbose_) + { + fprintf(stdout, "I: %s\n", message_.c_str()); + } + break; + + case InternalLevel_TRACE: + if (globalTrace_) + { + fprintf(stdout, "T: %s\n", message_.c_str()); + } + break; + + default: + fprintf(stderr, "Unknown log level (%d) for message: %s\n", level_, message_.c_str()); + } } void EnableInfoLevel(bool enabled) { - FLAGS_minloglevel = (enabled ? 0 : 1); + globalVerbose_ = enabled; } void EnableTraceLevel(bool enabled) { - if (enabled) - { - FLAGS_minloglevel = 0; - FLAGS_v = 1; - } - else - { - FLAGS_v = 0; - } - } - - void SetTargetFolder(const std::string& path) - { - FLAGS_logtostderr = false; - FLAGS_log_dir = path; + globalTrace_ = enabled; } } } -#else + +#else /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && + ORTHANC_ENABLE_LOGGING_STDIO == 0 && + ORTHANC_ENABLE_LOGGING == 1 */ /********************************************************* - * Use internal logger, not Google Log + * Internal logger of Orthanc, that mimics some + * behavior from Google Log. *********************************************************/ #include "OrthancException.h" #include "Enumerations.h" #include "Toolbox.h" +#if ORTHANC_SANDBOXED == 1 +# include <stdio.h> +#else +# include "SystemToolbox.h" +#endif + #include <fstream> #include <boost/filesystem.hpp> #include <boost/thread.hpp> - -#if BOOST_HAS_DATE_TIME == 1 -# include <boost/date_time/posix_time/posix_time.hpp> -#else -# error Boost::date_time is required -#endif +#include <boost/date_time/posix_time/posix_time.hpp> namespace { - struct LoggingState + struct LoggingContext { bool infoEnabled_; bool traceEnabled_; + std::string targetFile_; + std::string targetFolder_; std::ostream* error_; std::ostream* warning_; @@ -146,7 +244,7 @@ std::auto_ptr<std::ofstream> file_; - LoggingState() : + LoggingContext() : infoEnabled_(false), traceEnabled_(false), error_(&std::cerr), @@ -159,7 +257,7 @@ -static std::auto_ptr<LoggingState> loggingState_; +static std::auto_ptr<LoggingContext> loggingContext_; static boost::mutex loggingMutex_; @@ -186,7 +284,7 @@ boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); boost::filesystem::path root(directory); - boost::filesystem::path exe(Toolbox::GetPathToExecutable()); + boost::filesystem::path exe(SystemToolbox::GetPathToExecutable()); if (!boost::filesystem::exists(root) || !boost::filesystem::is_directory(root)) @@ -199,10 +297,10 @@ static_cast<int>(now.date().year()), now.date().month().as_number(), now.date().day().as_number(), - now.time_of_day().hours(), - now.time_of_day().minutes(), - now.time_of_day().seconds(), - Toolbox::GetProcessId()); + static_cast<int>(now.time_of_day().hours()), + static_cast<int>(now.time_of_day().minutes()), + static_cast<int>(now.time_of_day().seconds()), + SystemToolbox::GetProcessId()); std::string programName = exe.filename().replace_extension("").string(); @@ -211,9 +309,9 @@ } - static void PrepareLogFile(std::auto_ptr<std::ofstream>& file, - const std::string& suffix, - const std::string& directory) + static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file, + const std::string& suffix, + const std::string& directory) { boost::filesystem::path log, link; GetLogPath(log, link, suffix, directory); @@ -230,168 +328,247 @@ void Initialize() { boost::mutex::scoped_lock lock(loggingMutex_); - loggingState_.reset(new LoggingState); + loggingContext_.reset(new LoggingContext); } void Finalize() { boost::mutex::scoped_lock lock(loggingMutex_); - loggingState_.reset(NULL); + loggingContext_.reset(NULL); + } + + void Reset() + { + // Recover the old logging context + std::auto_ptr<LoggingContext> old; + + { + boost::mutex::scoped_lock lock(loggingMutex_); + if (loggingContext_.get() == NULL) + { + return; + } + else + { + old = loggingContext_; + + // Create a new logging context, + loggingContext_.reset(new LoggingContext); + } + } + + EnableInfoLevel(old->infoEnabled_); + EnableTraceLevel(old->traceEnabled_); + + if (!old->targetFolder_.empty()) + { + SetTargetFolder(old->targetFolder_); + } + else if (!old->targetFile_.empty()) + { + SetTargetFile(old->targetFile_); + } } void EnableInfoLevel(bool enabled) { boost::mutex::scoped_lock lock(loggingMutex_); - assert(loggingState_.get() != NULL); + assert(loggingContext_.get() != NULL); - loggingState_->infoEnabled_ = enabled; + loggingContext_->infoEnabled_ = enabled; + + if (!enabled) + { + // Also disable the "TRACE" level when info-level debugging is disabled + loggingContext_->traceEnabled_ = false; + } } void EnableTraceLevel(bool enabled) { boost::mutex::scoped_lock lock(loggingMutex_); - assert(loggingState_.get() != NULL); + assert(loggingContext_.get() != NULL); - loggingState_->traceEnabled_ = enabled; + loggingContext_->traceEnabled_ = enabled; if (enabled) { // Also enable the "INFO" level when trace-level debugging is enabled - loggingState_->infoEnabled_ = true; + loggingContext_->infoEnabled_ = true; + } + } + + + static void CheckFile(std::auto_ptr<std::ofstream>& f) + { + if (loggingContext_->file_.get() == NULL || + !loggingContext_->file_->is_open()) + { + throw OrthancException(ErrorCode_CannotWriteFile); } } void SetTargetFolder(const std::string& path) { boost::mutex::scoped_lock lock(loggingMutex_); - assert(loggingState_.get() != NULL); + assert(loggingContext_.get() != NULL); + + PrepareLogFolder(loggingContext_->file_, "" /* no suffix */, path); + CheckFile(loggingContext_->file_); - PrepareLogFile(loggingState_->file_, "" /* no suffix */, path); + loggingContext_->targetFile_.clear(); + loggingContext_->targetFolder_ = path; + loggingContext_->warning_ = loggingContext_->file_.get(); + loggingContext_->error_ = loggingContext_->file_.get(); + loggingContext_->info_ = loggingContext_->file_.get(); + } + - loggingState_->warning_ = loggingState_->file_.get(); - loggingState_->error_ = loggingState_->file_.get(); - loggingState_->info_ = loggingState_->file_.get(); + void SetTargetFile(const std::string& path) + { + boost::mutex::scoped_lock lock(loggingMutex_); + assert(loggingContext_.get() != NULL); + + loggingContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app)); + CheckFile(loggingContext_->file_); + + loggingContext_->targetFile_ = path; + loggingContext_->targetFolder_.clear(); + loggingContext_->warning_ = loggingContext_->file_.get(); + loggingContext_->error_ = loggingContext_->file_.get(); + loggingContext_->info_ = loggingContext_->file_.get(); } + InternalLogger::InternalLogger(const char* level, const char* file, int line) : lock_(loggingMutex_), stream_(&null_) // By default, logging to "/dev/null" is simulated { - if (loggingState_.get() == NULL) + if (loggingContext_.get() == NULL) { fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); return; } - LogLevel l = StringToLogLevel(level); - - if ((l == LogLevel_Info && !loggingState_->infoEnabled_) || - (l == LogLevel_Trace && !loggingState_->traceEnabled_)) + try { - // This logging level is disabled, directly exit and unlock - // the mutex to speed-up things. The stream is set to "/dev/null" - lock_.unlock(); - return; - } + LogLevel l = StringToLogLevel(level); + + if ((l == LogLevel_Info && !loggingContext_->infoEnabled_) || + (l == LogLevel_Trace && !loggingContext_->traceEnabled_)) + { + // This logging level is disabled, directly exit and unlock + // the mutex to speed-up things. The stream is set to "/dev/null" + lock_.unlock(); + return; + } - // Compute the header of the line, temporary release the lock as - // this is a time-consuming operation - lock_.unlock(); - std::string header; + // Compute the header of the line, temporary release the lock as + // this is a time-consuming operation + lock_.unlock(); + std::string header; - { - boost::filesystem::path path(file); - boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); - boost::posix_time::time_duration duration = now.time_of_day(); + { + boost::filesystem::path path(file); + boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration duration = now.time_of_day(); - /** - From Google Log documentation: + /** + From Google Log documentation: - "Log lines have this form: + "Log lines have this form: - Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... - where the fields are defined as follows: + where the fields are defined as follows: - L A single character, representing the log level (eg 'I' for INFO) - mm The month (zero padded; ie May is '05') - dd The day (zero padded) - hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds - threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux) - file The file name - line The line number - msg The user-supplied message" + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux) + file The file name + line The line number + msg The user-supplied message" - In this implementation, "threadid" is not printed. - **/ + In this implementation, "threadid" is not printed. + **/ - char date[32]; - sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ", - level[0], - now.date().month().as_number(), - now.date().day().as_number(), - duration.hours(), - duration.minutes(), - duration.seconds(), - static_cast<int>(duration.fractional_seconds())); + char date[40]; + sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ", + level[0], + now.date().month().as_number(), + now.date().day().as_number(), + static_cast<int>(duration.hours()), + static_cast<int>(duration.minutes()), + static_cast<int>(duration.seconds()), + static_cast<int>(duration.fractional_seconds())); - header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] "; - } + header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] "; + } - // The header is computed, we now re-lock the mutex to access - // the stream objects. Pay attention that "loggingState_", - // "infoEnabled_" or "traceEnabled_" might have changed while - // the mutex was unlocked. - lock_.lock(); + // The header is computed, we now re-lock the mutex to access + // the stream objects. Pay attention that "loggingContext_", + // "infoEnabled_" or "traceEnabled_" might have changed while + // the mutex was unlocked. + lock_.lock(); + + if (loggingContext_.get() == NULL) + { + fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); + return; + } - if (loggingState_.get() == NULL) - { - fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); - return; - } + switch (l) + { + case LogLevel_Error: + stream_ = loggingContext_->error_; + break; - switch (l) - { - case LogLevel_Error: - stream_ = loggingState_->error_; - break; + case LogLevel_Warning: + stream_ = loggingContext_->warning_; + break; - case LogLevel_Warning: - stream_ = loggingState_->warning_; - break; + case LogLevel_Info: + if (loggingContext_->infoEnabled_) + { + stream_ = loggingContext_->info_; + } + + break; - case LogLevel_Info: - if (loggingState_->infoEnabled_) - { - stream_ = loggingState_->info_; - } + case LogLevel_Trace: + if (loggingContext_->traceEnabled_) + { + stream_ = loggingContext_->info_; + } - break; + break; - case LogLevel_Trace: - if (loggingState_->traceEnabled_) - { - stream_ = loggingState_->info_; - } + default: + throw OrthancException(ErrorCode_InternalError); + } - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } + if (stream_ == &null_) + { + // The logging is disabled for this level. The stream is the + // "null_" member of this object, so we can release the global + // mutex. + lock_.unlock(); + } - if (stream_ == &null_) - { - // The logging is disabled for this level. The stream is the - // "null_" member of this object, so we can release the global - // mutex. - lock_.unlock(); + (*stream_) << header; } - - (*stream_) << header; + catch (...) + { + // Something is going really wrong, probably running out of + // memory. Fallback to a degraded mode. + stream_ = loggingContext_->error_; + (*stream_) << "E???? ??:??:??.?????? ] "; + } } @@ -410,6 +587,16 @@ } + void Flush() + { + boost::mutex::scoped_lock lock(loggingMutex_); + + if (loggingContext_.get() != NULL && + loggingContext_->file_.get() != NULL) + { + loggingContext_->file_->flush(); + } + } } }
--- a/Core/Logging.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Logging.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,18 +35,54 @@ #include <iostream> +#if !defined(ORTHANC_ENABLE_LOGGING) +# error The macro ORTHANC_ENABLE_LOGGING must be defined +#endif + +#if !defined(ORTHANC_ENABLE_LOGGING_PLUGIN) +# if ORTHANC_ENABLE_LOGGING == 1 +# error The macro ORTHANC_ENABLE_LOGGING_PLUGIN must be defined +# else +# define ORTHANC_ENABLE_LOGGING_PLUGIN 0 +# endif +#endif + +#if !defined(ORTHANC_ENABLE_LOGGING_STDIO) +# if ORTHANC_ENABLE_LOGGING == 1 +# error The macro ORTHANC_ENABLE_LOGGING_STDIO must be defined +# else +# define ORTHANC_ENABLE_LOGGING_STDIO 0 +# endif +#endif + +#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 +# include <orthanc/OrthancCPlugin.h> +#endif + +#include <boost/lexical_cast.hpp> + namespace Orthanc { namespace Logging { +#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 + void Initialize(OrthancPluginContext* context); +#else void Initialize(); +#endif void Finalize(); + void Reset(); + + void Flush(); + void EnableInfoLevel(bool enabled); void EnableTraceLevel(bool enabled); + void SetTargetFile(const std::string& path); + void SetTargetFolder(const std::string& path); struct NullStream : public std::ostream @@ -56,7 +93,8 @@ { } - std::ostream& operator<< (const std::string& message) + template <typename T> + std::ostream& operator<< (const T& message) { return *this; } @@ -70,18 +108,62 @@ # define LOG(level) ::Orthanc::Logging::NullStream() # define VLOG(level) ::Orthanc::Logging::NullStream() -#else /* ORTHANC_ENABLE_LOGGING == 1 */ + +#elif (ORTHANC_ENABLE_LOGGING_PLUGIN == 1 || \ + ORTHANC_ENABLE_LOGGING_STDIO == 1) + +# include <boost/noncopyable.hpp> +# define LOG(level) ::Orthanc::Logging::InternalLogger \ + (::Orthanc::Logging::InternalLevel_ ## level, __FILE__, __LINE__) +# define VLOG(level) ::Orthanc::Logging::InternalLogger \ + (::Orthanc::Logging::InternalLevel_TRACE, __FILE__, __LINE__) -#if ORTHANC_ENABLE_GOOGLE_LOG == 1 -# include <stdlib.h> // Including this fixes a problem in glog for recent releases of MinGW -# include <glog/logging.h> -#else +namespace Orthanc +{ + namespace Logging + { + enum InternalLevel + { + InternalLevel_ERROR, + InternalLevel_WARNING, + InternalLevel_INFO, + InternalLevel_TRACE + }; + + class InternalLogger : public boost::noncopyable + { + private: + InternalLevel level_; + std::string message_; + + public: + InternalLogger(InternalLevel level, + const char* file, + int line); + + ~InternalLogger(); + + template <typename T> + InternalLogger& operator<< (const T& message) + { + message_ += boost::lexical_cast<std::string>(message); + return *this; + } + }; + } +} + + + + +#else /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && + ORTHANC_ENABLE_LOGGING_STDIO == 0 && + ORTHANC_ENABLE_LOGGING == 1 */ + # include <boost/thread/mutex.hpp> # define LOG(level) ::Orthanc::Logging::InternalLogger(#level, __FILE__, __LINE__) # define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__) -#endif -#if ORTHANC_ENABLE_GOOGLE_LOG != 1 namespace Orthanc { namespace Logging @@ -100,13 +182,13 @@ ~InternalLogger(); - std::ostream& operator<< (const std::string& message) + template <typename T> + std::ostream& operator<< (const T& message) { - return (*stream_) << message; + return (*stream_) << boost::lexical_cast<std::string>(message); } }; } } -#endif #endif // ORTHANC_ENABLE_LOGGING
--- a/Core/Lua/LuaContext.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Lua/LuaContext.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +62,6 @@ return true; } - LuaContext& LuaContext::GetLuaContext(lua_State *state) { const void* value = GetGlobalVariable(state, "_LuaContext"); @@ -209,14 +209,36 @@ return true; } + void LuaContext::SetHttpHeaders(lua_State *state, int top) + { + this->httpClient_.ClearHeaders(); // always reset headers in case they have been set in a previous request + + if (lua_gettop(state) >= top) + { + Json::Value headers; + this->GetJson(headers, top, true); + + Json::Value::Members members = headers.getMemberNames(); + + for (Json::Value::Members::const_iterator + it = members.begin(); it != members.end(); ++it) + { + this->httpClient_.AddHeader(*it, headers[*it].asString()); + } + } + + } + + int LuaContext::CallHttpGet(lua_State *state) { LuaContext& that = GetLuaContext(state); // Check the types of the arguments int nArgs = lua_gettop(state); - if (nArgs != 1 || !lua_isstring(state, 1)) // URL + if ((nArgs < 1 || nArgs > 2) || // check args count + !lua_isstring(state, 1)) // URL is a string { LOG(ERROR) << "Lua: Bad parameters to HttpGet()"; lua_pushnil(state); @@ -227,6 +249,8 @@ const char* url = lua_tostring(state, 1); that.httpClient_.SetMethod(HttpMethod_Get); that.httpClient_.SetUrl(url); + that.httpClient_.GetBody().clear(); + that.SetHttpHeaders(state, 2); // Do the HTTP GET request if (!that.AnswerHttpQuery(state)) @@ -246,9 +270,9 @@ // Check the types of the arguments int nArgs = lua_gettop(state); - if ((nArgs != 1 && nArgs != 2) || - !lua_isstring(state, 1) || // URL - (nArgs >= 2 && !lua_isstring(state, 2))) // Body data + if ((nArgs < 1 || nArgs > 3) || // check arg count + !lua_isstring(state, 1) || // URL is a string + (nArgs >= 2 && (!lua_isstring(state, 2) && !lua_isnil(state, 2)))) // Body data is null or is a string { LOG(ERROR) << "Lua: Bad parameters to HttpPost() or HttpPut()"; lua_pushnil(state); @@ -259,10 +283,21 @@ const char* url = lua_tostring(state, 1); that.httpClient_.SetMethod(method); that.httpClient_.SetUrl(url); + that.SetHttpHeaders(state, 3); - if (nArgs >= 2) + if (nArgs >= 2 && !lua_isnil(state, 2)) { - that.httpClient_.SetBody(lua_tostring(state, 2)); + size_t bodySize = 0; + const char* bodyData = lua_tolstring(state, 2, &bodySize); + + if (bodySize == 0) + { + that.httpClient_.GetBody().clear(); + } + else + { + that.httpClient_.GetBody().assign(bodyData, bodySize); + } } else { @@ -298,7 +333,7 @@ // Check the types of the arguments int nArgs = lua_gettop(state); - if (nArgs != 1 || !lua_isstring(state, 1)) // URL + if (nArgs < 1 || nArgs > 2 || !lua_isstring(state, 1)) // URL { LOG(ERROR) << "Lua: Bad parameters to HttpDelete()"; lua_pushnil(state); @@ -309,6 +344,8 @@ const char* url = lua_tostring(state, 1); that.httpClient_.SetMethod(HttpMethod_Delete); that.httpClient_.SetUrl(url); + that.httpClient_.GetBody().clear(); + that.SetHttpHeaders(state, 2); // Do the HTTP DELETE request std::string s; @@ -497,6 +534,10 @@ // Lua can convert most types to strings by default. result = std::string(lua_tostring(lua_, top)); } + else if (lua_isboolean(lua_, top)) + { + result = lua_toboolean(lua_, top) ? true : false; + } else { LOG(WARNING) << "Unsupported Lua type when returning Json"; @@ -557,12 +598,14 @@ } +#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1 void LuaContext::Execute(EmbeddedResources::FileResourceId resource) { std::string command; EmbeddedResources::GetFileResource(command, resource); ExecuteInternal(NULL, command); } +#endif bool LuaContext::IsExistingFunction(const char* name)
--- a/Core/Lua/LuaContext.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Lua/LuaContext.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +33,22 @@ #pragma once +#if !defined(ORTHANC_ENABLE_LUA) +# error The macro ORTHANC_ENABLE_LUA must be defined +#endif + +#if !defined(ORTHANC_HAS_EMBEDDED_RESOURCES) +# error Macro ORTHANC_HAS_EMBEDDED_RESOURCES must be defined +#endif + +#if ORTHANC_ENABLE_LUA == 0 +# error The Lua support is disabled, cannot include this file +#endif + +#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1 +# include <EmbeddedResources.h> // Autogenerated file +#endif + #include "../HttpClient.h" extern "C" @@ -39,7 +56,6 @@ #include <lua.h> } -#include <EmbeddedResources.h> #include <boost/noncopyable.hpp> namespace Orthanc @@ -74,6 +90,8 @@ void GetJson(Json::Value& result, int top, bool keepStrings); + + void SetHttpHeaders(lua_State* state, int top); public: LuaContext(); @@ -94,7 +112,9 @@ void Execute(Json::Value& output, const std::string& command); +#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1 void Execute(EmbeddedResources::FileResourceId resource); +#endif bool IsExistingFunction(const char* name); @@ -104,11 +124,6 @@ httpClient_.SetCredentials(username, password); } - void SetHttpProxy(const std::string& proxy) - { - httpClient_.SetProxy(proxy); - } - void RegisterFunction(const char* name, lua_CFunction func);
--- a/Core/Lua/LuaFunctionCall.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Lua/LuaFunctionCall.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,4 +153,40 @@ throw OrthancException(ErrorCode_LuaReturnsNoString); } } + + + void LuaFunctionCall::PushStringMap(const std::map<std::string, std::string>& value) + { + Json::Value json = Json::objectValue; + + for (std::map<std::string, std::string>::const_iterator + it = value.begin(); it != value.end(); ++it) + { + json[it->first] = it->second; + } + + PushJson(json); + } + + + void LuaFunctionCall::PushDicom(const DicomMap& dicom) + { + DicomArray a(dicom); + PushDicom(a); + } + + + void LuaFunctionCall::PushDicom(const DicomArray& dicom) + { + Json::Value value = Json::objectValue; + + for (size_t i = 0; i < dicom.GetSize(); i++) + { + const DicomValue& v = dicom.GetElement(i).GetValue(); + std::string s = (v.IsNull() || v.IsBinary()) ? "" : v.GetContent(); + value[dicom.GetElement(i).GetTag().Format()] = s; + } + + PushJson(value); + } }
--- a/Core/Lua/LuaFunctionCall.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Lua/LuaFunctionCall.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,9 @@ #include "LuaContext.h" +#include "../DicomFormat/DicomArray.h" +#include "../DicomFormat/DicomMap.h" + #include <json/json.h> namespace Orthanc @@ -46,8 +50,14 @@ void CheckAlreadyExecuted(); + protected: void ExecuteInternal(int numOutputs); + lua_State* GetState() + { + return context_.lua_; + } + public: LuaFunctionCall(LuaContext& context, const char* functionName); @@ -62,6 +72,12 @@ void PushJson(const Json::Value& value); + void PushStringMap(const std::map<std::string, std::string>& value); + + void PushDicom(const DicomMap& dicom); + + void PushDicom(const DicomArray& dicom); + void Execute() { ExecuteInternal(0);
--- a/Core/MultiThreading/ILockable.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class ILockable : public boost::noncopyable - { - friend class Locker; - - protected: - virtual void Lock() = 0; - - virtual void Unlock() = 0; - - public: - virtual ~ILockable() - { - } - }; -}
--- a/Core/MultiThreading/IRunnableBySteps.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/MultiThreading/IRunnableBySteps.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/Locker.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ILockable.h" - -namespace Orthanc -{ - class Locker : public boost::noncopyable - { - private: - ILockable& lockable_; - - public: - Locker(ILockable& lockable) : lockable_(lockable) - { - lockable_.Lock(); - } - - virtual ~Locker() - { - lockable_.Unlock(); - } - }; -}
--- a/Core/MultiThreading/Mutex.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeaders.h" -#include "Mutex.h" - -#include "../OrthancException.h" - -#if defined(_WIN32) -#include <windows.h> -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) -#include <pthread.h> -#else -#error Support your platform here -#endif - -namespace Orthanc -{ -#if defined (_WIN32) - - struct Mutex::PImpl - { - CRITICAL_SECTION criticalSection_; - }; - - Mutex::Mutex() - { - pimpl_ = new PImpl; - ::InitializeCriticalSection(&pimpl_->criticalSection_); - } - - Mutex::~Mutex() - { - ::DeleteCriticalSection(&pimpl_->criticalSection_); - delete pimpl_; - } - - void Mutex::Lock() - { - ::EnterCriticalSection(&pimpl_->criticalSection_); - } - - void Mutex::Unlock() - { - ::LeaveCriticalSection(&pimpl_->criticalSection_); - } - - -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) - - struct Mutex::PImpl - { - pthread_mutex_t mutex_; - }; - - Mutex::Mutex() - { - pimpl_ = new PImpl; - - if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0) - { - delete pimpl_; - throw OrthancException(ErrorCode_InternalError); - } - } - - Mutex::~Mutex() - { - pthread_mutex_destroy(&pimpl_->mutex_); - delete pimpl_; - } - - void Mutex::Lock() - { - if (pthread_mutex_lock(&pimpl_->mutex_) != 0) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - void Mutex::Unlock() - { - if (pthread_mutex_unlock(&pimpl_->mutex_) != 0) - { - throw OrthancException(ErrorCode_InternalError); - } - } - -#else -#error Support your plateform here -#endif -}
--- a/Core/MultiThreading/Mutex.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ILockable.h" - -namespace Orthanc -{ - class Mutex : public ILockable - { - private: - struct PImpl; - - PImpl *pimpl_; - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - Mutex(); - - ~Mutex(); - }; -}
--- a/Core/MultiThreading/ReaderWriterLock.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeaders.h" -#include "ReaderWriterLock.h" - -#include <boost/thread/shared_mutex.hpp> - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation - // modules. - - class ReaderLockable : public ILockable - { - private: - boost::shared_mutex& lock_; - - protected: - virtual void Lock() - { - lock_.lock_shared(); - } - - virtual void Unlock() - { - lock_.unlock_shared(); - } - - public: - ReaderLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - }; - - - class WriterLockable : public ILockable - { - private: - boost::shared_mutex& lock_; - - protected: - virtual void Lock() - { - lock_.lock(); - } - - virtual void Unlock() - { - lock_.unlock(); - } - - public: - WriterLockable(boost::shared_mutex& lock) : lock_(lock) - { - } - - }; - } - - struct ReaderWriterLock::PImpl - { - boost::shared_mutex lock_; - ReaderLockable reader_; - WriterLockable writer_; - - PImpl() : reader_(lock_), writer_(lock_) - { - } - }; - - - ReaderWriterLock::ReaderWriterLock() - { - pimpl_ = new PImpl; - } - - - ReaderWriterLock::~ReaderWriterLock() - { - delete pimpl_; - } - - - ILockable& ReaderWriterLock::ForReader() - { - return pimpl_->reader_; - } - - - ILockable& ReaderWriterLock::ForWriter() - { - return pimpl_->writer_; - } -}
--- a/Core/MultiThreading/ReaderWriterLock.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ILockable.h" - -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class ReaderWriterLock : public boost::noncopyable - { - private: - struct PImpl; - - PImpl *pimpl_; - - public: - ReaderWriterLock(); - - virtual ~ReaderWriterLock(); - - ILockable& ForReader(); - - ILockable& ForWriter(); - }; -}
--- a/Core/MultiThreading/RunnableWorkersPool.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/MultiThreading/RunnableWorkersPool.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -52,25 +53,37 @@ { while (that->continue_) { - std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100)); - if (obj.get() != NULL) + try { - try + std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100)); + if (obj.get() != NULL) { IRunnableBySteps& runnable = *dynamic_cast<IRunnableBySteps*>(obj.get()); - + bool wishToContinue = runnable.Step(); - + if (wishToContinue) { // The runnable wishes to continue, reinsert it at the beginning of the queue that->queue_.Enqueue(obj.release()); } } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception in a pool of working threads: " << e.What(); - } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Exception while handling some runnable object: " << e.What(); + } + catch (std::bad_alloc&) + { + LOG(ERROR) << "Not enough memory to handle some runnable object"; + } + catch (std::exception& e) + { + LOG(ERROR) << "std::exception while handling some runnable object: " << e.what(); + } + catch (...) + { + LOG(ERROR) << "Native exception while handling some runnable object"; } } } @@ -105,7 +118,7 @@ { pimpl_->continue_ = true; - if (countWorkers <= 0) + if (countWorkers == 0) { throw OrthancException(ErrorCode_ParameterOutOfRange); }
--- a/Core/MultiThreading/RunnableWorkersPool.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/MultiThreading/RunnableWorkersPool.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +48,7 @@ void Stop(); public: - RunnableWorkersPool(size_t countWorkers); + explicit RunnableWorkersPool(size_t countWorkers); ~RunnableWorkersPool();
--- a/Core/MultiThreading/Semaphore.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/MultiThreading/Semaphore.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +39,10 @@ namespace Orthanc { - Semaphore::Semaphore(unsigned int count) : count_(count) + Semaphore::Semaphore(unsigned int count) : + count_(count) { - if (count == 0) + if (count_ == 0) { throw OrthancException(ErrorCode_ParameterOutOfRange); }
--- a/Core/MultiThreading/Semaphore.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/MultiThreading/Semaphore.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -43,12 +44,30 @@ unsigned int count_; boost::mutex mutex_; boost::condition_variable condition_; + + void Release(); + + void Acquire(); public: explicit Semaphore(unsigned int count); - void Release(); + class Locker : public boost::noncopyable + { + private: + Semaphore& that_; - void Acquire(); + public: + explicit Locker(Semaphore& that) : + that_(that) + { + that_.Acquire(); + } + + ~Locker() + { + that_.Release(); + } + }; }; }
--- a/Core/MultiThreading/SharedMessageQueue.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/MultiThreading/SharedMessageQueue.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -185,4 +186,24 @@ boost::mutex::scoped_lock lock(mutex_); isFifo_ = false; } + + void SharedMessageQueue::Clear() + { + boost::mutex::scoped_lock lock(mutex_); + + if (queue_.empty()) + { + return; + } + else + { + while (!queue_.empty()) + { + std::auto_ptr<IDynamicObject> message(queue_.front()); + queue_.pop_front(); + } + + emptied_.notify_all(); + } + } }
--- a/Core/MultiThreading/SharedMessageQueue.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/MultiThreading/SharedMessageQueue.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,5 +79,7 @@ void SetFifoPolicy(); void SetLifoPolicy(); + + void Clear(); }; }
--- a/Core/OrthancException.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/OrthancException.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +46,7 @@ HttpStatus httpStatus_; public: - OrthancException(ErrorCode errorCode) : + explicit OrthancException(ErrorCode errorCode) : errorCode_(errorCode), httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Pkcs11.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,309 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "Pkcs11.h" + + +#if defined(OPENSSL_NO_RSA) || defined(OPENSSL_NO_EC) || defined(OPENSSL_NO_ECDSA) || defined(OPENSSL_NO_ECDH) +# error OpenSSL was compiled without support for RSA, EC, ECDSA or ECDH +#endif + + +#include "Logging.h" +#include "OrthancException.h" +#include "SystemToolbox.h" + +extern "C" +{ +# include <libp11/engine.h> // This is P11's "engine.h" +# include <libp11/libp11.h> +} + +#include <openssl/engine.h> + + +namespace Orthanc +{ + namespace Pkcs11 + { + static const char* PKCS11_ENGINE_ID = "pkcs11"; + static const char* PKCS11_ENGINE_NAME = "PKCS#11 for Orthanc"; + static const ENGINE_CMD_DEFN PKCS11_ENGINE_COMMANDS[] = + { + { + CMD_MODULE_PATH, + "MODULE_PATH", + "Specifies the path to the PKCS#11 module shared library", + ENGINE_CMD_FLAG_STRING + }, + { + CMD_PIN, + "PIN", + "Specifies the pin code", + ENGINE_CMD_FLAG_STRING + }, + { + CMD_VERBOSE, + "VERBOSE", + "Print additional details", + ENGINE_CMD_FLAG_NO_INPUT + }, + { + CMD_LOAD_CERT_CTRL, + "LOAD_CERT_CTRL", + "Get the certificate from card", + ENGINE_CMD_FLAG_INTERNAL + }, + { + 0, + NULL, + NULL, + 0 + } + }; + + + static bool pkcs11Initialized_ = false; + static ENGINE_CTX *context_ = NULL; + + static int EngineInitialize(ENGINE* engine) + { + if (context_ == NULL) + { + return 0; + } + else + { + return pkcs11_init(context_); + } + } + + + static int EngineFinalize(ENGINE* engine) + { + if (context_ == NULL) + { + return 0; + } + else + { + return pkcs11_finish(context_); + } + } + + + static int EngineDestroy(ENGINE* engine) + { + return (context_ == NULL ? 0 : 1); + } + + + static int EngineControl(ENGINE *engine, + int command, + long i, + void *p, + void (*f) ()) + { + if (context_ == NULL) + { + return 0; + } + else + { + return pkcs11_engine_ctrl(context_, command, i, p, f); + } + } + + + static EVP_PKEY *EngineLoadPublicKey(ENGINE *engine, + const char *s_key_id, + UI_METHOD *ui_method, + void *callback_data) + { + if (context_ == NULL) + { + return 0; + } + else + { + return pkcs11_load_public_key(context_, s_key_id, ui_method, callback_data); + } + } + + + static EVP_PKEY *EngineLoadPrivateKey(ENGINE *engine, + const char *s_key_id, + UI_METHOD *ui_method, + void *callback_data) + { + if (context_ == NULL) + { + return 0; + } + else + { + return pkcs11_load_private_key(context_, s_key_id, ui_method, callback_data); + } + } + + + static ENGINE* LoadEngine() + { + // This function creates an engine for PKCS#11 and inspired by + // the "ENGINE_load_dynamic" function from OpenSSL, in file + // "crypto/engine/eng_dyn.c" + + ENGINE* engine = ENGINE_new(); + if (!engine) + { + LOG(ERROR) << "Cannot create an OpenSSL engine for PKCS#11"; + throw OrthancException(ErrorCode_InternalError); + } + + // Create a PKCS#11 context using libp11 + context_ = pkcs11_new(); + if (!context_) + { + LOG(ERROR) << "Cannot create a libp11 context for PKCS#11"; + ENGINE_free(engine); + throw OrthancException(ErrorCode_InternalError); + } + + if (!ENGINE_set_id(engine, PKCS11_ENGINE_ID) || + !ENGINE_set_name(engine, PKCS11_ENGINE_NAME) || + !ENGINE_set_cmd_defns(engine, PKCS11_ENGINE_COMMANDS) || + + // Register the callback functions + !ENGINE_set_init_function(engine, EngineInitialize) || + !ENGINE_set_finish_function(engine, EngineFinalize) || + !ENGINE_set_destroy_function(engine, EngineDestroy) || + !ENGINE_set_ctrl_function(engine, EngineControl) || + !ENGINE_set_load_pubkey_function(engine, EngineLoadPublicKey) || + !ENGINE_set_load_privkey_function(engine, EngineLoadPrivateKey) || + + !ENGINE_set_RSA(engine, PKCS11_get_rsa_method()) || + !ENGINE_set_ECDSA(engine, PKCS11_get_ecdsa_method()) || + !ENGINE_set_ECDH(engine, PKCS11_get_ecdh_method()) || + +#if OPENSSL_VERSION_NUMBER >= 0x10100002L + !ENGINE_set_EC(engine, PKCS11_get_ec_key_method()) || +#endif + + // Make OpenSSL know about our PKCS#11 engine + !ENGINE_add(engine)) + { + LOG(ERROR) << "Cannot initialize the OpenSSL engine for PKCS#11"; + pkcs11_finish(context_); + ENGINE_free(engine); + throw OrthancException(ErrorCode_InternalError); + } + + // If the "ENGINE_add" worked, it gets a structural + // reference. We release our just-created reference. + ENGINE_free(engine); + + return ENGINE_by_id(PKCS11_ENGINE_ID); + } + + + bool IsInitialized() + { + return pkcs11Initialized_; + } + + const char* GetEngineIdentifier() + { + return PKCS11_ENGINE_ID; + } + + void Initialize(const std::string& module, + const std::string& pin, + bool verbose) + { + if (pkcs11Initialized_) + { + LOG(ERROR) << "The PKCS#11 engine has already been initialized"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (module.empty() || + !SystemToolbox::IsRegularFile(module)) + { + LOG(ERROR) << "The PKCS#11 module must be a path to one shared library (DLL or .so)"; + throw OrthancException(ErrorCode_InexistentFile); + } + + ENGINE* engine = LoadEngine(); + if (!engine) + { + LOG(ERROR) << "Cannot create an OpenSSL engine for PKCS#11"; + throw OrthancException(ErrorCode_InternalError); + } + + if (!ENGINE_ctrl_cmd_string(engine, "MODULE_PATH", module.c_str(), 0)) + { + LOG(ERROR) << "Cannot configure the OpenSSL dynamic engine for PKCS#11"; + throw OrthancException(ErrorCode_InternalError); + } + + if (verbose) + { + ENGINE_ctrl_cmd_string(engine, "VERBOSE", NULL, 0); + } + + if (!pin.empty() && + !ENGINE_ctrl_cmd_string(engine, "PIN", pin.c_str(), 0)) + { + LOG(ERROR) << "Cannot set the PIN code for PKCS#11"; + throw OrthancException(ErrorCode_InternalError); + } + + if (!ENGINE_init(engine)) + { + LOG(ERROR) << "Cannot initialize the OpenSSL dynamic engine for PKCS#11"; + throw OrthancException(ErrorCode_InternalError); + } + + LOG(WARNING) << "The PKCS#11 engine has been successfully initialized"; + pkcs11Initialized_ = true; + } + + + void Finalize() + { + // Nothing to do, the unregistration of the engine is + // automatically done by OpenSSL + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Pkcs11.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if !defined(ORTHANC_ENABLE_PKCS11) +# error The macro ORTHANC_ENABLE_PKCS11 must be defined +#endif + +#if !defined(ORTHANC_ENABLE_SSL) +# error The macro ORTHANC_ENABLE_SSL must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error This file cannot be used in sandboxed environments +#endif + +#if ORTHANC_ENABLE_PKCS11 != 1 || ORTHANC_ENABLE_SSL != 1 +# error This file cannot be used if OpenSSL or PKCS#11 support is disabled +#endif + + +#include <string> + +namespace Orthanc +{ + namespace Pkcs11 + { + const char* GetEngineIdentifier(); + + bool IsInitialized(); + + void Initialize(const std::string& module, + const std::string& pin, + bool verbose); + + void Finalize(); + } +}
--- a/Core/PrecompiledHeaders.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/PrecompiledHeaders.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/PrecompiledHeaders.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -48,14 +49,28 @@ #include <json/value.h> -#if ORTHANC_PUGIXML_ENABLED == 1 -#include <pugixml.hpp> +#if ORTHANC_ENABLE_PUGIXML == 1 +# include <pugixml.hpp> #endif #include "Enumerations.h" #include "Logging.h" #include "OrthancException.h" #include "Toolbox.h" -#include "Uuid.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +// Headers from DCMTK used in Orthanc headers +# include <dcmtk/dcmdata/dcdatset.h> +# include <dcmtk/dcmdata/dcfilefo.h> +# include <dcmtk/dcmdata/dcmetinf.h> +# include <dcmtk/dcmdata/dcpixseq.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_NETWORKING == 1 +# include "DicomNetworking/DicomServer.h" + +// Headers from DCMTK used in Orthanc headers +# include <dcmtk/dcmnet/dimse.h> +#endif #endif
--- a/Core/RestApi/RestApi.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApi.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -185,7 +186,7 @@ { RestApiOutput wrappedOutput(output, method); -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 { // Look if the client wishes XML answers instead of JSON // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3
--- a/Core/RestApi/RestApi.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApi.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiCall.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiCall.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiDeleteCall.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiGetCall.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiGetCall.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiHierarchy.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiHierarchy.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiOutput.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +36,7 @@ #include "../Logging.h" #include "../OrthancException.h" +#include "../Toolbox.h" #include <boost/lexical_cast.hpp> @@ -91,10 +93,10 @@ if (convertJsonToXml_) { -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 std::string s; Toolbox::JsonToXml(s, value); - output_.SetContentType("application/xml"); + output_.SetContentType("application/xml; charset=utf-8"); output_.Answer(s); #else LOG(ERROR) << "Orthanc was compiled without XML support"; @@ -104,7 +106,7 @@ else { Json::StyledWriter writer; - output_.SetContentType("application/json"); + output_.SetContentType("application/json; charset=utf-8"); output_.Answer(writer.write(value)); }
--- a/Core/RestApi/RestApiOutput.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiOutput.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiPath.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiPath.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiPostCall.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/RestApi/RestApiPutCall.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/Connection.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/Connection.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -163,7 +163,8 @@ if (error == SQLITE_ERROR) { #if ORTHANC_SQLITE_STANDALONE != 1 - LOG(ERROR) << "SQLite execute error: " << sqlite3_errmsg(db_); + LOG(ERROR) << "SQLite execute error: " << sqlite3_errmsg(db_) + << " (" << sqlite3_extended_errcode(db_) << ")"; #endif throw OrthancSQLiteException(ErrorCode_SQLiteExecute);
--- a/Core/SQLite/Connection.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/Connection.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -39,13 +39,11 @@ #include "Statement.h" #include "IScalarFunction.h" +#include "SQLiteTypes.h" #include <string> #include <map> -struct sqlite3; -struct sqlite3_stmt; - #define SQLITE_FROM_HERE ::Orthanc::SQLite::StatementId(__FILE__, __LINE__) namespace Orthanc
--- a/Core/SQLite/FunctionContext.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/FunctionContext.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ { FunctionContext::FunctionContext(struct sqlite3_context* context, int argc, - struct ::Mem** argv) + Internals::SQLiteValue** argv) { assert(context != NULL); assert(argc >= 0);
--- a/Core/SQLite/FunctionContext.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/FunctionContext.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Redistribution and use in source and binary forms, with or without @@ -36,9 +36,6 @@ #include "Statement.h" -struct sqlite3_context; -struct Mem; // This corresponds to the opaque type "sqlite3_value" - namespace Orthanc { namespace SQLite @@ -50,14 +47,14 @@ private: struct sqlite3_context* context_; unsigned int argc_; - struct ::Mem** argv_; + Internals::SQLiteValue** argv_; void CheckIndex(unsigned int index) const; public: FunctionContext(struct sqlite3_context* context, int argc, - struct ::Mem** argv); + Internals::SQLiteValue** argv); ColumnType GetColumnType(unsigned int index) const;
--- a/Core/SQLite/IScalarFunction.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/IScalarFunction.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/ITransaction.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/ITransaction.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/NonCopyable.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/NonCopyable.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/OrthancSQLiteException.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/OrthancSQLiteException.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -38,6 +38,11 @@ #pragma once +#if ORTHANC_ENABLE_SQLITE != 1 +# error Macro ORTHANC_ENABLE_SQLITE must be set to 1 to use SQLite +#endif + + #if ORTHANC_SQLITE_STANDALONE == 1 #include <stdexcept>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SQLite/SQLiteTypes.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, + * Medical Physics Department, CHU of Liege, Belgium + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of the CHU of Liege, nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +struct sqlite3; +struct sqlite3_context; +struct sqlite3_stmt; + +#if !defined(ORTHANC_SQLITE_VERSION) +#error Please define macro ORTHANC_SQLITE_VERSION +#endif + + +/** + * "sqlite3_value" is defined as: + * - "typedef struct Mem sqlite3_value;" up to SQLite <= 3.18.2 + * - "typedef struct sqlite3_value sqlite3_value;" since SQLite >= 3.19.0. + * We create our own copy of this typedef to get around this API incompatibility. + * https://github.com/mackyle/sqlite/commit/db1d90df06a78264775a14d22c3361eb5b42be17 + **/ + +#if ORTHANC_SQLITE_VERSION < 3019000 +struct Mem; +#else +struct sqlite3_value; +#endif + +namespace Orthanc +{ + namespace SQLite + { + namespace Internals + { +#if ORTHANC_SQLITE_VERSION < 3019000 + typedef struct ::Mem SQLiteValue; +#else + typedef struct ::sqlite3_value SQLiteValue; +#endif + } + } +}
--- a/Core/SQLite/Statement.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/Statement.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -46,8 +46,21 @@ #include <stdio.h> #include <algorithm> -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../Logging.h" +#if (ORTHANC_SQLITE_STANDALONE == 1) +// Trace logging is disabled if this SQLite wrapper is used +// independently of Orthanc +# define LOG_CREATE(message); +# define LOG_APPLY(message); +#elif defined(NDEBUG) +// Trace logging is disabled in release builds +# include "../Logging.h" +# define LOG_CREATE(message); +# define LOG_APPLY(message); +#else +// Trace logging is enabled in debug builds +# include "../Logging.h" +# define LOG_CREATE(message) VLOG(1) << "SQLite::Statement create: " << message; +# define LOG_APPLY(message); // VLOG(1) << "SQLite::Statement apply: " << message; #endif #include "sqlite3.h" @@ -56,6 +69,7 @@ #define snprintf _snprintf #endif + namespace Orthanc { namespace SQLite @@ -103,6 +117,7 @@ reference_(database.GetCachedStatement(id, sql.c_str())) { Reset(true); + LOG_CREATE(sql); } @@ -112,6 +127,7 @@ reference_(database.GetCachedStatement(id, sql)) { Reset(true); + LOG_CREATE(sql); } @@ -119,6 +135,7 @@ const std::string& sql) : reference_(database.GetWrappedObject(), sql.c_str()) { + LOG_CREATE(sql); } @@ -126,23 +143,20 @@ const char* sql) : reference_(database.GetWrappedObject(), sql) { + LOG_CREATE(sql); } bool Statement::Run() { -#if ORTHANC_SQLITE_STANDALONE != 1 - VLOG(1) << "SQLite::Statement::Run " << sqlite3_sql(GetStatement()); -#endif + LOG_APPLY(sqlite3_sql(GetStatement())); return CheckError(sqlite3_step(GetStatement()), ErrorCode_SQLiteCannotRun) == SQLITE_DONE; } bool Statement::Step() { -#if ORTHANC_SQLITE_STANDALONE != 1 - VLOG(1) << "SQLite::Statement::Step " << sqlite3_sql(GetStatement()); -#endif + LOG_APPLY(sqlite3_sql(GetStatement())); return CheckError(sqlite3_step(GetStatement()), ErrorCode_SQLiteCannotStep) == SQLITE_ROW; }
--- a/Core/SQLite/Statement.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/Statement.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -46,11 +46,9 @@ #include <stdint.h> #if ORTHANC_BUILD_UNIT_TESTS == 1 -#include <gtest/gtest_prod.h> +# include <gtest/gtest_prod.h> #endif -struct sqlite3_stmt; - namespace Orthanc {
--- a/Core/SQLite/StatementId.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/StatementId.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/StatementId.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/StatementReference.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -82,7 +82,8 @@ if (error != SQLITE_OK) { #if ORTHANC_SQLITE_STANDALONE != 1 - LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database); + LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database) + << " (" << sqlite3_extended_errcode(database) << ")"; #endif throw OrthancSQLiteException(ErrorCode_SQLitePrepareStatement);
--- a/Core/SQLite/StatementReference.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/StatementReference.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -38,13 +38,12 @@ #pragma once #include "NonCopyable.h" +#include "SQLiteTypes.h" #include <stdint.h> #include <cassert> #include <stdlib.h> -struct sqlite3; -struct sqlite3_stmt; namespace Orthanc {
--- a/Core/SQLite/Transaction.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/Transaction.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/SQLite/Transaction.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store * - * Copyright (C) 2012-2015 Sebastien Jodogne <s.jodogne@gmail.com>, + * Copyright (C) 2012-2016 Sebastien Jodogne <s.jodogne@gmail.com>, * Medical Physics Department, CHU of Liege, Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SerializationToolbox.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,375 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SerializationToolbox.h" + +#include "OrthancException.h" + +namespace Orthanc +{ + namespace SerializationToolbox + { + std::string ReadString(const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return value[field.c_str()].asString(); + } + } + + + int ReadInteger(const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + (value[field.c_str()].type() != Json::intValue && + value[field.c_str()].type() != Json::uintValue)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return value[field.c_str()].asInt(); + } + } + + + unsigned int ReadUnsignedInteger(const Json::Value& value, + const std::string& field) + { + int tmp = ReadInteger(value, field); + + if (tmp < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return static_cast<unsigned int>(tmp); + } + } + + + bool ReadBoolean(const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::booleanValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return value[field.c_str()].asBool(); + } + } + + + void ReadArrayOfStrings(std::vector<std::string>& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& arr = value[field.c_str()]; + + target.resize(arr.size()); + + for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++) + { + if (arr[i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target[i] = arr[i].asString(); + } + } + } + + + void ReadListOfStrings(std::list<std::string>& target, + const Json::Value& value, + const std::string& field) + { + std::vector<std::string> tmp; + ReadArrayOfStrings(tmp, value, field); + + target.clear(); + for (size_t i = 0; i < tmp.size(); i++) + { + target.push_back(tmp[i]); + } + } + + + void ReadSetOfStrings(std::set<std::string>& target, + const Json::Value& value, + const std::string& field) + { + std::vector<std::string> tmp; + ReadArrayOfStrings(tmp, value, field); + + target.clear(); + for (size_t i = 0; i < tmp.size(); i++) + { + target.insert(tmp[i]); + } + } + + + void ReadSetOfTags(std::set<DicomTag>& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& arr = value[field.c_str()]; + + target.clear(); + + for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++) + { + DicomTag tag(0, 0); + + if (arr[i].type() != Json::stringValue || + !DicomTag::ParseHexadecimal(tag, arr[i].asCString())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target.insert(tag); + } + } + } + + + void ReadMapOfStrings(std::map<std::string, std::string>& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& source = value[field.c_str()]; + + target.clear(); + + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& tmp = source[members[i]]; + + if (tmp.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target[members[i]] = tmp.asString(); + } + } + } + + + void ReadMapOfTags(std::map<DicomTag, std::string>& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& source = value[field.c_str()]; + + target.clear(); + + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& tmp = source[members[i]]; + + DicomTag tag(0, 0); + + if (!DicomTag::ParseHexadecimal(tag, members[i].c_str()) || + tmp.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target[tag] = tmp.asString(); + } + } + } + + + void WriteArrayOfStrings(Json::Value& target, + const std::vector<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 (size_t i = 0; i < values.size(); i++) + { + value.append(values[i]); + } + } + + + void WriteSetOfStrings(Json::Value& target, + const std::set<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::set<std::string>::const_iterator it = values.begin(); + it != values.end(); ++it) + { + value.append(*it); + } + } + + + void WriteSetOfTags(Json::Value& target, + const std::set<DicomTag>& tags, + 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::set<DicomTag>::const_iterator it = tags.begin(); + it != tags.end(); ++it) + { + value.append(it->Format()); + } + } + + + void WriteMapOfStrings(Json::Value& target, + const std::map<std::string, 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::objectValue; + + for (std::map<std::string, std::string>::const_iterator + it = values.begin(); it != values.end(); ++it) + { + value[it->first] = it->second; + } + } + + + void WriteMapOfTags(Json::Value& target, + const std::map<DicomTag, 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::objectValue; + + for (std::map<DicomTag, std::string>::const_iterator + it = values.begin(); it != values.end(); ++it) + { + value[it->first.Format()] = it->second; + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SerializationToolbox.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,102 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomFormat/DicomTag.h" + +#include <json/value.h> +#include <list> +#include <map> + +namespace Orthanc +{ + namespace SerializationToolbox + { + std::string ReadString(const Json::Value& value, + const std::string& field); + + int ReadInteger(const Json::Value& value, + const std::string& field); + + unsigned int ReadUnsignedInteger(const Json::Value& value, + const std::string& field); + + bool ReadBoolean(const Json::Value& value, + const std::string& field); + + void ReadArrayOfStrings(std::vector<std::string>& target, + const Json::Value& value, + const std::string& field); + + void ReadListOfStrings(std::list<std::string>& target, + const Json::Value& value, + const std::string& field); + + void ReadSetOfStrings(std::set<std::string>& target, + const Json::Value& value, + const std::string& field); + + void ReadSetOfTags(std::set<DicomTag>& target, + const Json::Value& value, + const std::string& field); + + void ReadMapOfStrings(std::map<std::string, std::string>& values, + const Json::Value& target, + const std::string& field); + + void ReadMapOfTags(std::map<DicomTag, std::string>& values, + const Json::Value& target, + const std::string& field); + + void WriteArrayOfStrings(Json::Value& target, + const std::vector<std::string>& values, + const std::string& field); + + void WriteSetOfStrings(Json::Value& target, + const std::set<std::string>& values, + const std::string& field); + + void WriteSetOfTags(Json::Value& target, + const std::set<DicomTag>& tags, + const std::string& field); + + void WriteMapOfStrings(Json::Value& target, + const std::map<std::string, std::string>& values, + const std::string& field); + + void WriteMapOfTags(Json::Value& target, + const std::map<DicomTag, std::string>& values, + const std::string& field); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SharedLibrary.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,136 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SharedLibrary.h" + +#include "Logging.h" +#include "OrthancException.h" + +#include <boost/filesystem.hpp> + +#if defined(_WIN32) +#include <windows.h> +#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#include <dlfcn.h> +#else +#error Support your platform here +#endif + +namespace Orthanc +{ + SharedLibrary::SharedLibrary(const std::string& path) : + path_(path), + handle_(NULL) + { +#if defined(_WIN32) + handle_ = ::LoadLibraryA(path_.c_str()); + if (handle_ == NULL) + { + LOG(ERROR) << "LoadLibrary(" << path_ << ") failed: Error " << ::GetLastError(); + throw OrthancException(ErrorCode_SharedLibrary); + } + +#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) + handle_ = ::dlopen(path_.c_str(), RTLD_NOW); + if (handle_ == NULL) + { + std::string explanation; + const char *tmp = ::dlerror(); + if (tmp) + { + explanation = ": Error " + std::string(tmp); + } + + LOG(ERROR) << "dlopen(" << path_ << ") failed" << explanation; + throw OrthancException(ErrorCode_SharedLibrary); + } + +#else +#error Support your platform here +#endif + } + + SharedLibrary::~SharedLibrary() + { + if (handle_) + { +#if defined(_WIN32) + ::FreeLibrary((HMODULE)handle_); +#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) + ::dlclose(handle_); +#else +#error Support your platform here +#endif + } + } + + + SharedLibrary::FunctionPointer SharedLibrary::GetFunctionInternal(const std::string& name) + { + if (!handle_) + { + throw OrthancException(ErrorCode_InternalError); + } + +#if defined(_WIN32) + return ::GetProcAddress((HMODULE)handle_, name.c_str()); +#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) + return ::dlsym(handle_, name.c_str()); +#else +#error Support your platform here +#endif + } + + + SharedLibrary::FunctionPointer SharedLibrary::GetFunction(const std::string& name) + { + SharedLibrary::FunctionPointer result = GetFunctionInternal(name); + + if (result == NULL) + { + LOG(ERROR) << "Shared library does not expose function \"" << name << "\""; + throw OrthancException(ErrorCode_SharedLibrary); + } + else + { + return result; + } + } + + + bool SharedLibrary::HasFunction(const std::string& name) + { + return GetFunctionInternal(name) != NULL; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SharedLibrary.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,82 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The namespace SystemToolbox cannot be used in sandboxed environments +#endif + +#if defined(_WIN32) +#include <windows.h> +#endif + +#include <string> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class SharedLibrary : public boost::noncopyable + { + public: +#if defined(_WIN32) + typedef FARPROC FunctionPointer; +#else + typedef void* FunctionPointer; +#endif + + private: + std::string path_; + void *handle_; + + FunctionPointer GetFunctionInternal(const std::string& name); + + public: + explicit SharedLibrary(const std::string& path); + + ~SharedLibrary(); + + const std::string& GetPath() const + { + return path_; + } + + bool HasFunction(const std::string& name); + + FunctionPointer GetFunction(const std::string& name); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SystemToolbox.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,581 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SystemToolbox.h" + + +#if defined(_WIN32) +# include <windows.h> +# include <process.h> // For "_spawnvp()" and "_getpid()" +#else +# include <unistd.h> // For "execvp()" +# include <sys/wait.h> // For "waitpid()" +#endif + + +#if defined(__APPLE__) && defined(__MACH__) +# include <mach-o/dyld.h> /* _NSGetExecutablePath */ +# include <limits.h> /* PATH_MAX */ +#endif + + +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) +# include <limits.h> /* PATH_MAX */ +# include <signal.h> +# include <unistd.h> +#endif + + +#if defined(__OpenBSD__) +# include <sys/sysctl.h> // For "sysctl", "CTL_KERN" and "KERN_PROC_ARGS" +#endif + + +#include "Logging.h" +#include "OrthancException.h" +#include "Toolbox.h" + +#include <boost/filesystem.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread.hpp> + + +namespace Orthanc +{ + static bool finish_; + static ServerBarrierEvent barrierEvent_; + +#if defined(_WIN32) + static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType) + { + // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx + finish_ = true; + return true; + } +#else + static void SignalHandler(int signal) + { + if (signal == SIGHUP) + { + barrierEvent_ = ServerBarrierEvent_Reload; + } + + finish_ = true; + } +#endif + + + static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag) + { +#if defined(_WIN32) + SetConsoleCtrlHandler(ConsoleControlHandler, true); +#else + signal(SIGINT, SignalHandler); + signal(SIGQUIT, SignalHandler); + signal(SIGTERM, SignalHandler); + signal(SIGHUP, SignalHandler); +#endif + + // Active loop that awakens every 100ms + finish_ = false; + barrierEvent_ = ServerBarrierEvent_Stop; + while (!(*stopFlag || finish_)) + { + SystemToolbox::USleep(100 * 1000); + } + +#if defined(_WIN32) + SetConsoleCtrlHandler(ConsoleControlHandler, false); +#else + signal(SIGINT, NULL); + signal(SIGQUIT, NULL); + signal(SIGTERM, NULL); + signal(SIGHUP, NULL); +#endif + + return barrierEvent_; + } + + + ServerBarrierEvent SystemToolbox::ServerBarrier(const bool& stopFlag) + { + return ServerBarrierInternal(&stopFlag); + } + + + ServerBarrierEvent SystemToolbox::ServerBarrier() + { + const bool stopFlag = false; + return ServerBarrierInternal(&stopFlag); + } + + + void SystemToolbox::USleep(uint64_t microSeconds) + { +#if defined(_WIN32) + ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000))); +#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__native_client__) + usleep(microSeconds); +#else +#error Support your platform here +#endif + } + + + static std::streamsize GetStreamSize(std::istream& f) + { + // http://www.cplusplus.com/reference/iostream/istream/tellg/ + f.seekg(0, std::ios::end); + std::streamsize size = f.tellg(); + f.seekg(0, std::ios::beg); + + return size; + } + + + void SystemToolbox::ReadFile(std::string& content, + const std::string& path) + { + if (!IsRegularFile(path)) + { + LOG(ERROR) << "The path does not point to a regular file: " << path; + throw OrthancException(ErrorCode_RegularFileExpected); + } + + boost::filesystem::ifstream f; + f.open(path, std::ifstream::in | std::ifstream::binary); + if (!f.good()) + { + throw OrthancException(ErrorCode_InexistentFile); + } + + std::streamsize size = GetStreamSize(f); + content.resize(static_cast<size_t>(size)); + if (size != 0) + { + f.read(reinterpret_cast<char*>(&content[0]), size); + } + + f.close(); + } + + + bool SystemToolbox::ReadHeader(std::string& header, + const std::string& path, + size_t headerSize) + { + if (!IsRegularFile(path)) + { + LOG(ERROR) << "The path does not point to a regular file: " << path; + throw OrthancException(ErrorCode_RegularFileExpected); + } + + boost::filesystem::ifstream f; + f.open(path, std::ifstream::in | std::ifstream::binary); + if (!f.good()) + { + throw OrthancException(ErrorCode_InexistentFile); + } + + bool full = true; + + { + std::streamsize size = GetStreamSize(f); + if (size <= 0) + { + headerSize = 0; + full = false; + } + else if (static_cast<size_t>(size) < headerSize) + { + headerSize = static_cast<size_t>(size); // Truncate to the size of the file + full = false; + } + } + + header.resize(headerSize); + if (headerSize != 0) + { + f.read(reinterpret_cast<char*>(&header[0]), headerSize); + } + + f.close(); + + return full; + } + + + void SystemToolbox::WriteFile(const void* content, + size_t size, + const std::string& path) + { + boost::filesystem::ofstream f; + f.open(path, std::ofstream::out | std::ofstream::binary); + if (!f.good()) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + if (size != 0) + { + f.write(reinterpret_cast<const char*>(content), size); + + if (!f.good()) + { + f.close(); + throw OrthancException(ErrorCode_FileStorageCannotWrite); + } + } + + f.close(); + } + + + void SystemToolbox::WriteFile(const std::string& content, + const std::string& path) + { + WriteFile(content.size() > 0 ? content.c_str() : NULL, + content.size(), path); + } + + + void SystemToolbox::RemoveFile(const std::string& path) + { + if (boost::filesystem::exists(path)) + { + if (IsRegularFile(path)) + { + boost::filesystem::remove(path); + } + else + { + throw OrthancException(ErrorCode_RegularFileExpected); + } + } + } + + + uint64_t SystemToolbox::GetFileSize(const std::string& path) + { + try + { + return static_cast<uint64_t>(boost::filesystem::file_size(path)); + } + catch (boost::filesystem::filesystem_error&) + { + throw OrthancException(ErrorCode_InexistentFile); + } + } + + + void SystemToolbox::MakeDirectory(const std::string& path) + { + if (boost::filesystem::exists(path)) + { + if (!boost::filesystem::is_directory(path)) + { + throw OrthancException(ErrorCode_DirectoryOverFile); + } + } + else + { + if (!boost::filesystem::create_directories(path)) + { + throw OrthancException(ErrorCode_MakeDirectory); + } + } + } + + + bool SystemToolbox::IsExistingFile(const std::string& path) + { + return boost::filesystem::exists(path); + } + + +#if defined(_WIN32) + static std::string GetPathToExecutableInternal() + { + // Yes, this is ugly, but there is no simple way to get the + // required buffer size, so we use a big constant + std::vector<char> buffer(32768); + /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1)); + return std::string(&buffer[0]); + } + +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) + static std::string GetPathToExecutableInternal() + { + // NOTE: For FreeBSD, using KERN_PROC_PATHNAME might be a better alternative + + std::vector<char> buffer(PATH_MAX + 1); + ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1); + if (bytes == 0) + { + throw OrthancException(ErrorCode_PathToExecutable); + } + + return std::string(&buffer[0]); + } + +#elif defined(__APPLE__) && defined(__MACH__) + static std::string GetPathToExecutableInternal() + { + char pathbuf[PATH_MAX + 1]; + unsigned int bufsize = static_cast<int>(sizeof(pathbuf)); + + _NSGetExecutablePath( pathbuf, &bufsize); + + return std::string(pathbuf); + } + +#elif defined(__OpenBSD__) + static std::string GetPathToExecutableInternal() + { + // This is an adapted version of the patch proposed in issue #64 + // without an explicit call to "malloc()" to prevent memory leak + // https://bitbucket.org/sjodogne/orthanc/issues/64/add-openbsd-support + // https://stackoverflow.com/q/31494901/881731 + + const int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; + + size_t len; + if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1) + { + throw OrthancException(ErrorCode_PathToExecutable); + } + + std::string tmp; + tmp.resize(len); + + char** buffer = reinterpret_cast<char**>(&tmp[0]); + + if (sysctl(mib, 4, buffer, &len, NULL, 0) == -1) + { + throw OrthancException(ErrorCode_PathToExecutable); + } + else + { + return std::string(buffer[0]); + } + } + +#else +#error Support your platform here +#endif + + + std::string SystemToolbox::GetPathToExecutable() + { + boost::filesystem::path p(GetPathToExecutableInternal()); + return boost::filesystem::absolute(p).string(); + } + + + std::string SystemToolbox::GetDirectoryOfExecutable() + { + boost::filesystem::path p(GetPathToExecutableInternal()); + return boost::filesystem::absolute(p.parent_path()).string(); + } + + + void SystemToolbox::ExecuteSystemCommand(const std::string& command, + const std::vector<std::string>& arguments) + { + // Convert the arguments as a C array + std::vector<char*> args(arguments.size() + 2); + + args.front() = const_cast<char*>(command.c_str()); + + for (size_t i = 0; i < arguments.size(); i++) + { + args[i + 1] = const_cast<char*>(arguments[i].c_str()); + } + + args.back() = NULL; + + int status; + +#if defined(_WIN32) + // http://msdn.microsoft.com/en-us/library/275khfab.aspx + status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0])); + +#else + int pid = fork(); + + if (pid == -1) + { + // Error in fork() +#if ORTHANC_ENABLE_LOGGING == 1 + LOG(ERROR) << "Cannot fork a child process"; +#endif + + throw OrthancException(ErrorCode_SystemCommand); + } + else if (pid == 0) + { + // Execute the system command in the child process + execvp(command.c_str(), &args[0]); + + // We should never get here + _exit(1); + } + else + { + // Wait for the system command to exit + waitpid(pid, &status, 0); + } +#endif + + if (status != 0) + { +#if ORTHANC_ENABLE_LOGGING == 1 + LOG(ERROR) << "System command failed with status code " << status; +#endif + + throw OrthancException(ErrorCode_SystemCommand); + } + } + + + int SystemToolbox::GetProcessId() + { +#if defined(_WIN32) + return static_cast<int>(_getpid()); +#else + return static_cast<int>(getpid()); +#endif + } + + + bool SystemToolbox::IsRegularFile(const std::string& path) + { + namespace fs = boost::filesystem; + + try + { + if (fs::exists(path)) + { + fs::file_status status = fs::status(path); + return (status.type() == boost::filesystem::regular_file || + status.type() == boost::filesystem::reparse_file); // Fix BitBucket issue #11 + } + } + catch (fs::filesystem_error&) + { + } + + return false; + } + + + FILE* SystemToolbox::OpenFile(const std::string& path, + FileMode mode) + { +#if defined(_WIN32) + // TODO Deal with special characters by converting to the current locale +#endif + + const char* m; + switch (mode) + { + case FileMode_ReadBinary: + m = "rb"; + break; + + case FileMode_WriteBinary: + m = "wb"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + return fopen(path.c_str(), m); + } + + + static boost::posix_time::ptime GetNow(bool utc) + { + if (utc) + { + return boost::posix_time::second_clock::universal_time(); + } + else + { + return boost::posix_time::second_clock::local_time(); + } + } + + + std::string SystemToolbox::GetNowIsoString(bool utc) + { + return boost::posix_time::to_iso_string(GetNow(utc)); + } + + + void SystemToolbox::GetNowDicom(std::string& date, + std::string& time, + bool utc) + { + boost::posix_time::ptime now = GetNow(utc); + tm tm = boost::posix_time::to_tm(now); + + char s[32]; + sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + date.assign(s); + + // TODO milliseconds + sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0); + time.assign(s); + } + + + unsigned int SystemToolbox::GetHardwareConcurrency() + { + // Get the number of available hardware threads (e.g. number of + // CPUs or cores or hyperthreading units) + unsigned int threads = boost::thread::hardware_concurrency(); + + if (threads <= 0) + { + return 1; + } + else + { + return threads; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SystemToolbox.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,104 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The namespace SystemToolbox cannot be used in sandboxed environments +#endif + +#include "Enumerations.h" + +#include <vector> +#include <string> +#include <stdint.h> + +namespace Orthanc +{ + namespace SystemToolbox + { + void USleep(uint64_t microSeconds); + + ServerBarrierEvent ServerBarrier(const bool& stopFlag); + + ServerBarrierEvent ServerBarrier(); + + void ReadFile(std::string& content, + const std::string& path); + + bool ReadHeader(std::string& header, + const std::string& path, + size_t headerSize); + + void WriteFile(const void* content, + size_t size, + const std::string& path); + + void WriteFile(const std::string& content, + const std::string& path); + + void RemoveFile(const std::string& path); + + uint64_t GetFileSize(const std::string& path); + + void MakeDirectory(const std::string& path); + + bool IsExistingFile(const std::string& path); + + std::string GetPathToExecutable(); + + std::string GetDirectoryOfExecutable(); + + void ExecuteSystemCommand(const std::string& command, + const std::vector<std::string>& arguments); + + int GetProcessId(); + + bool IsRegularFile(const std::string& path); + + FILE* OpenFile(const std::string& path, + FileMode mode); + + std::string GetNowIsoString(bool utc); + + void GetNowDicom(std::string& date, + std::string& time, + bool utc); + + unsigned int GetHardwareConcurrency(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/TemporaryFile.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,95 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "TemporaryFile.h" + +#include "SystemToolbox.h" +#include "Toolbox.h" + +#include <boost/filesystem.hpp> + +namespace Orthanc +{ + static std::string CreateTemporaryPath(const char* extension) + { +#if BOOST_HAS_FILESYSTEM_V3 == 1 + boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path(); +#elif defined(__linux__) + boost::filesystem::path tmpDir("/tmp"); +#else +#error Support your platform here +#endif + + // We use UUID to create unique path to temporary files + std::string filename = "Orthanc-" + Orthanc::Toolbox::GenerateUuid(); + + if (extension != NULL) + { + filename.append(extension); + } + + tmpDir /= filename; + return tmpDir.string(); + } + + + TemporaryFile::TemporaryFile() : + path_(CreateTemporaryPath(NULL)) + { + } + + + TemporaryFile::TemporaryFile(const char* extension) : + path_(CreateTemporaryPath(extension)) + { + } + + + TemporaryFile::~TemporaryFile() + { + boost::filesystem::remove(path_); + } + + + void TemporaryFile::Write(const std::string& content) + { + SystemToolbox::WriteFile(content, path_); + } + + + void TemporaryFile::Read(std::string& content) const + { + SystemToolbox::ReadFile(content, path_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/TemporaryFile.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The class TemporaryFile cannot be used in sandboxed environments +#endif + +#include <string> + +namespace Orthanc +{ + class TemporaryFile + { + private: + std::string path_; + + public: + TemporaryFile(); + + TemporaryFile(const char* extension); + + ~TemporaryFile(); + + const std::string& GetPath() const + { + return path_; + } + + void Write(const std::string& content); + + void Read(std::string& content) const; + }; +}
--- a/Core/Toolbox.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Toolbox.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,57 +37,38 @@ #include "OrthancException.h" #include "Logging.h" +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/regex.hpp> +#include <boost/uuid/sha1.hpp> + #include <string> #include <stdint.h> #include <string.h> -#include <boost/filesystem.hpp> -#include <boost/filesystem/fstream.hpp> -#include <boost/uuid/sha1.hpp> -#include <boost/lexical_cast.hpp> #include <algorithm> #include <ctype.h> -#if BOOST_HAS_DATE_TIME == 1 -#include <boost/date_time/posix_time/posix_time.hpp> -#endif -#if BOOST_HAS_REGEX == 1 -#include <boost/regex.hpp> +#if ORTHANC_ENABLE_MD5 == 1 +# include "../Resources/ThirdParty/md5/md5.h" #endif -#if defined(_WIN32) -#include <windows.h> -#include <process.h> // For "_spawnvp()" and "_getpid()" -#else -#include <unistd.h> // For "execvp()" -#include <sys/wait.h> // For "waitpid()" -#endif - -#if defined(__APPLE__) && defined(__MACH__) -#include <mach-o/dyld.h> /* _NSGetExecutablePath */ -#include <limits.h> /* PATH_MAX */ +#if ORTHANC_ENABLE_BASE64 == 1 +# include "../Resources/ThirdParty/base64/base64.h" #endif -#if defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) -#include <limits.h> /* PATH_MAX */ -#include <signal.h> -#include <unistd.h> -#endif - -#if BOOST_HAS_LOCALE != 1 -#error Since version 0.7.6, Orthanc entirely relies on boost::locale +#if ORTHANC_ENABLE_LOCALE == 1 +# include <boost/locale.hpp> #endif -#include <boost/locale.hpp> - - -#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1 -#include "../Resources/ThirdParty/md5/md5.h" -#endif - - -#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 -#include "../Resources/ThirdParty/base64/base64.h" +#if ORTHANC_ENABLE_SSL == 1 +// For OpenSSL initialization and finalization +# include <openssl/conf.h> +# include <openssl/engine.h> +# include <openssl/err.h> +# include <openssl/evp.h> +# include <openssl/ssl.h> #endif @@ -103,81 +85,105 @@ #endif -#if ORTHANC_PUGIXML_ENABLED == 1 -#include "ChunkedBuffer.h" -#include <pugixml.hpp> +#if defined(_WIN32) +# include <windows.h> // For ::Sleep +#endif + + +#if ORTHANC_ENABLE_PUGIXML == 1 +# include "ChunkedBuffer.h" +# include <pugixml.hpp> #endif +// Inclusions for UUID +// http://stackoverflow.com/a/1626302 + +extern "C" +{ +#if defined(_WIN32) +# include <rpc.h> +#else +# include <uuid/uuid.h> +#endif +} + + + namespace Orthanc { - static bool finish; - -#if defined(_WIN32) - static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType) - { - // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx - finish = true; - return true; - } -#else - static void SignalHandler(int) + void Toolbox::LinesIterator::FindEndOfLine() { - finish = true; + lineEnd_ = lineStart_; + + while (lineEnd_ < content_.size() && + content_[lineEnd_] != '\n' && + content_[lineEnd_] != '\r') + { + lineEnd_ += 1; + } } -#endif + - void Toolbox::USleep(uint64_t microSeconds) + Toolbox::LinesIterator::LinesIterator(const std::string& content) : + content_(content), + lineStart_(0) { -#if defined(_WIN32) - ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000))); -#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) - usleep(microSeconds); -#else -#error Support your platform here -#endif + FindEndOfLine(); } - - static void ServerBarrierInternal(const bool* stopFlag) + + bool Toolbox::LinesIterator::GetLine(std::string& target) const { -#if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, true); -#else - signal(SIGINT, SignalHandler); - signal(SIGQUIT, SignalHandler); - signal(SIGTERM, SignalHandler); -#endif - - // Active loop that awakens every 100ms - finish = false; - while (!(*stopFlag || finish)) + assert(lineStart_ <= content_.size() && + lineEnd_ <= content_.size() && + lineStart_ <= lineEnd_); + + if (lineStart_ == content_.size()) { - Toolbox::USleep(100 * 1000); + return false; } - -#if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, false); -#else - signal(SIGINT, NULL); - signal(SIGQUIT, NULL); - signal(SIGTERM, NULL); -#endif + else + { + target = content_.substr(lineStart_, lineEnd_ - lineStart_); + return true; + } } + + void Toolbox::LinesIterator::Next() + { + lineStart_ = lineEnd_; - void Toolbox::ServerBarrier(const bool& stopFlag) - { - ServerBarrierInternal(&stopFlag); + if (lineStart_ != content_.size()) + { + assert(content_[lineStart_] == '\r' || + content_[lineStart_] == '\n'); + + char second; + + if (content_[lineStart_] == '\r') + { + second = '\n'; + } + else + { + second = '\r'; + } + + lineStart_ += 1; + + if (lineStart_ < content_.size() && + content_[lineStart_] == second) + { + lineStart_ += 1; + } + + FindEndOfLine(); + } } - void Toolbox::ServerBarrier() - { - const bool stopFlag = false; - ServerBarrierInternal(&stopFlag); - } - - + void Toolbox::ToUpperCase(std::string& s) { std::transform(s.begin(), s.end(), s.begin(), toupper); @@ -205,82 +211,6 @@ } - void Toolbox::ReadFile(std::string& content, - const std::string& path) - { - if (!boost::filesystem::is_regular_file(path)) - { - LOG(ERROR) << "The path does not point to a regular file: " << path; - throw OrthancException(ErrorCode_RegularFileExpected); - } - - boost::filesystem::ifstream f; - f.open(path, std::ifstream::in | std::ifstream::binary); - if (!f.good()) - { - throw OrthancException(ErrorCode_InexistentFile); - } - - // http://www.cplusplus.com/reference/iostream/istream/tellg/ - f.seekg(0, std::ios::end); - std::streamsize size = f.tellg(); - f.seekg(0, std::ios::beg); - - content.resize(size); - if (size != 0) - { - f.read(reinterpret_cast<char*>(&content[0]), size); - } - - f.close(); - } - - - void Toolbox::WriteFile(const void* content, - size_t size, - const std::string& path) - { - boost::filesystem::ofstream f; - f.open(path, std::ofstream::binary); - if (!f.good()) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - if (size != 0) - { - f.write(reinterpret_cast<const char*>(content), size); - } - - f.close(); - } - - - void Toolbox::WriteFile(const std::string& content, - const std::string& path) - { - WriteFile(content.size() > 0 ? content.c_str() : NULL, - content.size(), path); - } - - - void Toolbox::RemoveFile(const std::string& path) - { - if (boost::filesystem::exists(path)) - { - if (boost::filesystem::is_regular_file(path)) - { - boost::filesystem::remove(path); - } - else - { - throw OrthancException(ErrorCode_RegularFileExpected); - } - } - } - - - void Toolbox::SplitUriComponents(UriComponents& components, const std::string& uri) { @@ -413,6 +343,8 @@ contentType = "application/json"; else if (!strcmp(extension, "pdf")) contentType = "application/pdf"; + else if (!strcmp(extension, "wasm")) + contentType = "application/wasm"; // Images types else if (!strcmp(extension, "jpg") || !strcmp(extension, "jpeg")) @@ -448,21 +380,7 @@ } - - uint64_t Toolbox::GetFileSize(const std::string& path) - { - try - { - return static_cast<uint64_t>(boost::filesystem::file_size(path)); - } - catch (boost::filesystem::filesystem_error&) - { - throw OrthancException(ErrorCode_InexistentFile); - } - } - - -#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1 +#if ORTHANC_ENABLE_MD5 == 1 static char GetHexadecimalCharacter(uint8_t value) { assert(value < 16); @@ -494,16 +412,16 @@ void Toolbox::ComputeMD5(std::string& result, const void* data, - size_t length) + size_t size) { md5_state_s state; md5_init(&state); - if (length > 0) + if (size > 0) { md5_append(&state, reinterpret_cast<const md5_byte_t*>(data), - static_cast<int>(length)); + static_cast<int>(size)); } md5_byte_t actualHash[16]; @@ -519,7 +437,7 @@ #endif -#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 +#if ORTHANC_ENABLE_BASE64 == 1 void Toolbox::EncodeBase64(std::string& result, const std::string& data) { @@ -529,12 +447,23 @@ void Toolbox::DecodeBase64(std::string& result, const std::string& data) { + for (size_t i = 0; i < data.length(); i++) + { + if (!isalnum(data[i]) && + data[i] != '+' && + data[i] != '/' && + data[i] != '=') + { + // This is not a valid character for a Base64 string + throw OrthancException(ErrorCode_BadFileFormat); + } + } + result = base64_decode(data); } -# if BOOST_HAS_REGEX == 1 - void Toolbox::DecodeDataUriScheme(std::string& mime, + bool Toolbox::DecodeDataUriScheme(std::string& mime, std::string& content, const std::string& source) { @@ -546,71 +475,26 @@ { mime = what[1]; DecodeBase64(content, what[2]); + return true; } else { - throw OrthancException(ErrorCode_BadFileFormat); + return false; } } -# endif + + + void Toolbox::EncodeDataUriScheme(std::string& result, + const std::string& mime, + const std::string& content) + { + result = "data:" + mime + ";base64," + base64_encode(content); + } #endif - -#if defined(_WIN32) - static std::string GetPathToExecutableInternal() - { - // Yes, this is ugly, but there is no simple way to get the - // required buffer size, so we use a big constant - std::vector<char> buffer(32768); - /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1)); - return std::string(&buffer[0]); - } - -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) - static std::string GetPathToExecutableInternal() - { - std::vector<char> buffer(PATH_MAX + 1); - ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1); - if (bytes == 0) - { - throw OrthancException(ErrorCode_PathToExecutable); - } - - return std::string(&buffer[0]); - } - -#elif defined(__APPLE__) && defined(__MACH__) - static std::string GetPathToExecutableInternal() - { - char pathbuf[PATH_MAX + 1]; - unsigned int bufsize = static_cast<int>(sizeof(pathbuf)); - - _NSGetExecutablePath( pathbuf, &bufsize); - - return std::string(pathbuf); - } - -#else -#error Support your platform here -#endif - - - std::string Toolbox::GetPathToExecutable() - { - boost::filesystem::path p(GetPathToExecutableInternal()); - return boost::filesystem::absolute(p).string(); - } - - - std::string Toolbox::GetDirectoryOfExecutable() - { - boost::filesystem::path p(GetPathToExecutableInternal()); - return boost::filesystem::absolute(p.parent_path()).string(); - } - - +#if ORTHANC_ENABLE_LOCALE == 1 static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding) { switch (sourceEncoding) @@ -677,8 +561,10 @@ throw OrthancException(ErrorCode_NotImplemented); } } +#endif +#if ORTHANC_ENABLE_LOCALE == 1 std::string Toolbox::ConvertToUtf8(const std::string& source, Encoding sourceEncoding) { @@ -705,8 +591,10 @@ return ConvertToAscii(source); } } +#endif + - +#if ORTHANC_ENABLE_LOCALE == 1 std::string Toolbox::ConvertFromUtf8(const std::string& source, Encoding targetEncoding) { @@ -733,8 +621,32 @@ return ConvertToAscii(source); } } +#endif + bool Toolbox::IsAsciiString(const void* data, + size_t size) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(data); + + for (size_t i = 0; i < size; i++, p++) + { + if (*p > 127 || *p == 0 || iscntrl(*p)) + { + return false; + } + } + + return true; + } + + + bool Toolbox::IsAsciiString(const std::string& s) + { + return IsAsciiString(s.c_str(), s.size()); + } + + std::string Toolbox::ConvertToAscii(const std::string& source) { std::string result; @@ -751,14 +663,16 @@ return result; } + void Toolbox::ComputeSHA1(std::string& result, - const std::string& data) + const void* data, + size_t size) { boost::uuids::detail::sha1 sha1; - if (data.size() > 0) + if (size > 0) { - sha1.process_bytes(&data[0], data.size()); + sha1.process_bytes(data, size); } unsigned int digest[5]; @@ -777,6 +691,20 @@ digest[4]); } + void Toolbox::ComputeSHA1(std::string& result, + const std::string& data) + { + if (data.size() > 0) + { + ComputeSHA1(result, data.c_str(), data.size()); + } + else + { + ComputeSHA1(result, NULL, 0); + } + } + + bool Toolbox::IsSHA1(const char* str, size_t size) { @@ -855,30 +783,6 @@ } -#if BOOST_HAS_DATE_TIME == 1 - std::string Toolbox::GetNowIsoString() - { - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - return boost::posix_time::to_iso_string(now); - } - - void Toolbox::GetNowDicom(std::string& date, - std::string& time) - { - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - tm tm = boost::posix_time::to_tm(now); - - char s[32]; - sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - date.assign(s); - - // TODO milliseconds - sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0); - time.assign(s); - } -#endif - - std::string Toolbox::StripSpaces(const std::string& source) { size_t first = 0; @@ -979,7 +883,6 @@ } -#if BOOST_HAS_REGEX == 1 std::string Toolbox::WildcardToRegularExpression(const std::string& source) { // TODO - Speed up this with a regular expression @@ -1007,8 +910,6 @@ return result; } -#endif - void Toolbox::TokenizeString(std::vector<std::string>& result, @@ -1036,32 +937,7 @@ } - void Toolbox::MakeDirectory(const std::string& path) - { - if (boost::filesystem::exists(path)) - { - if (!boost::filesystem::is_directory(path)) - { - throw OrthancException(ErrorCode_DirectoryOverFile); - } - } - else - { - if (!boost::filesystem::create_directories(path)) - { - throw OrthancException(ErrorCode_MakeDirectory); - } - } - } - - - bool Toolbox::IsExistingFile(const std::string& path) - { - return boost::filesystem::exists(path); - } - - -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 class ChunkedBufferWriter : public pugi::xml_writer { private: @@ -1183,64 +1059,6 @@ #endif - void Toolbox::ExecuteSystemCommand(const std::string& command, - const std::vector<std::string>& arguments) - { - // Convert the arguments as a C array - std::vector<char*> args(arguments.size() + 2); - - args.front() = const_cast<char*>(command.c_str()); - - for (size_t i = 0; i < arguments.size(); i++) - { - args[i + 1] = const_cast<char*>(arguments[i].c_str()); - } - - args.back() = NULL; - - int status; - -#if defined(_WIN32) - // http://msdn.microsoft.com/en-us/library/275khfab.aspx - status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0])); - -#else - int pid = fork(); - - if (pid == -1) - { - // Error in fork() -#if ORTHANC_ENABLE_LOGGING == 1 - LOG(ERROR) << "Cannot fork a child process"; -#endif - - throw OrthancException(ErrorCode_SystemCommand); - } - else if (pid == 0) - { - // Execute the system command in the child process - execvp(command.c_str(), &args[0]); - - // We should never get here - _exit(1); - } - else - { - // Wait for the system command to exit - waitpid(pid, &status, 0); - } -#endif - - if (status != 0) - { -#if ORTHANC_ENABLE_LOGGING == 1 - LOG(ERROR) << "System command failed with status code " << status; -#endif - - throw OrthancException(ErrorCode_SystemCommand); - } - } - bool Toolbox::IsInteger(const std::string& str) { @@ -1348,15 +1166,402 @@ return str.compare(0, prefix.size(), prefix) == 0; } } + + + static bool IsUnreservedCharacter(char c) + { + // This function checks whether "c" is an unserved character + // wrt. an URI percent-encoding + // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding%5Fin%5Fa%5FURI + + return ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '_' || + c == '.' || + c == '~'); + } + + void Toolbox::UriEncode(std::string& target, + const std::string& source) + { + // Estimate the length of the percent-encoded URI + size_t length = 0; + + for (size_t i = 0; i < source.size(); i++) + { + if (IsUnreservedCharacter(source[i])) + { + length += 1; + } + else + { + // This character must be percent-encoded + length += 3; + } + } + + target.clear(); + target.reserve(length); + + for (size_t i = 0; i < source.size(); i++) + { + if (IsUnreservedCharacter(source[i])) + { + target.push_back(source[i]); + } + else + { + // This character must be percent-encoded + uint8_t byte = static_cast<uint8_t>(source[i]); + uint8_t a = byte >> 4; + uint8_t b = byte & 0x0f; + + target.push_back('%'); + target.push_back(a < 10 ? a + '0' : a - 10 + 'A'); + target.push_back(b < 10 ? b + '0' : b - 10 + 'A'); + } + } + } + + + static bool HasField(const Json::Value& json, + const std::string& key, + Json::ValueType expectedType) + { + if (json.type() != Json::objectValue || + !json.isMember(key)) + { + return false; + } + else if (json[key].type() == expectedType) + { + return true; + } + else + { + throw OrthancException(ErrorCode_BadParameterType); + } + } + + + std::string Toolbox::GetJsonStringField(const Json::Value& json, + const std::string& key, + const std::string& defaultValue) + { + if (HasField(json, key, Json::stringValue)) + { + return json[key].asString(); + } + else + { + return defaultValue; + } + } + + + bool Toolbox::GetJsonBooleanField(const ::Json::Value& json, + const std::string& key, + bool defaultValue) + { + if (HasField(json, key, Json::booleanValue)) + { + return json[key].asBool(); + } + else + { + return defaultValue; + } + } + + + int Toolbox::GetJsonIntegerField(const ::Json::Value& json, + const std::string& key, + int defaultValue) + { + if (HasField(json, key, Json::intValue)) + { + return json[key].asInt(); + } + else + { + return defaultValue; + } + } + + + unsigned int Toolbox::GetJsonUnsignedIntegerField(const ::Json::Value& json, + const std::string& key, + unsigned int defaultValue) + { + int v = GetJsonIntegerField(json, key, defaultValue); + + if (v < 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return static_cast<unsigned int>(v); + } + } + + + bool Toolbox::IsUuid(const std::string& str) + { + if (str.size() != 36) + { + return false; + } + + for (size_t i = 0; i < str.length(); i++) + { + if (i == 8 || i == 13 || i == 18 || i == 23) + { + if (str[i] != '-') + return false; + } + else + { + if (!isalnum(str[i])) + return false; + } + } + + return true; + } - int Toolbox::GetProcessId() + bool Toolbox::StartsWithUuid(const std::string& str) + { + if (str.size() < 36) + { + return false; + } + + if (str.size() == 36) + { + return IsUuid(str); + } + + assert(str.size() > 36); + if (!isspace(str[36])) + { + return false; + } + + return IsUuid(str.substr(0, 36)); + } + + +#if ORTHANC_ENABLE_LOCALE == 1 + static std::auto_ptr<std::locale> globalLocale_; + + static bool SetGlobalLocale(const char* locale) + { + globalLocale_.reset(NULL); + + try + { + if (locale == NULL) + { + LOG(WARNING) << "Falling back to system-wide default locale"; + globalLocale_.reset(new std::locale()); + } + else + { + LOG(INFO) << "Using locale: \"" << locale << "\" for case-insensitive comparison of strings"; + globalLocale_.reset(new std::locale(locale)); + } + } + catch (std::runtime_error&) + { + } + + return (globalLocale_.get() != NULL); + } + + void Toolbox::InitializeGlobalLocale(const char* locale) + { + // Make Orthanc use English, United States locale + // Linux: use "en_US.UTF-8" + // Windows: use "" + // Wine: use NULL + +#if defined(__MINGW32__) + // Visibly, there is no support of locales in MinGW yet + // http://mingw.5.n7.nabble.com/How-to-use-std-locale-global-with-MinGW-correct-td33048.html + static const char* DEFAULT_LOCALE = NULL; +#elif defined(_WIN32) + // For Windows: use default locale (using "en_US" does not work) + static const char* DEFAULT_LOCALE = ""; +#else + // For Linux & cie + static const char* DEFAULT_LOCALE = "en_US.UTF-8"; +#endif + + bool ok; + + if (locale == NULL) + { + ok = SetGlobalLocale(DEFAULT_LOCALE); + +#if defined(__MINGW32__) + LOG(WARNING) << "This is a MinGW build, case-insensitive comparison of " + << "strings with accents will not work outside of Wine"; +#endif + } + else + { + ok = SetGlobalLocale(locale); + } + + if (!ok && + !SetGlobalLocale(NULL)) + { + LOG(ERROR) << "Cannot initialize global locale"; + throw OrthancException(ErrorCode_InternalError); + } + + } + + + void Toolbox::FinalizeGlobalLocale() { -#if defined(_WIN32) - return static_cast<int>(_getpid()); + globalLocale_.reset(); + } + + + std::string Toolbox::ToUpperCaseWithAccents(const std::string& source) + { + if (globalLocale_.get() == NULL) + { + LOG(ERROR) << "No global locale was set, call Toolbox::InitializeGlobalLocale()"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + /** + * A few notes about locales: + * + * (1) We don't use "case folding": + * http://www.boost.org/doc/libs/1_64_0/libs/locale/doc/html/conversions.html + * + * Characters are made uppercase one by one. This is because, in + * static builds, we are using iconv, which is visibly not + * supported correctly (TODO: Understand why). Case folding seems + * to be working correctly if using the default backend under + * Linux (ICU or POSIX?). If one wishes to use case folding, one + * would use: + * + * boost::locale::generator gen; + * std::locale::global(gen(DEFAULT_LOCALE)); + * return boost::locale::to_upper(source); + * + * (2) The function "boost::algorithm::to_upper_copy" does not + * make use of the "std::locale::global()". We therefore create a + * global variable "globalLocale_". + * + * (3) The variant of "boost::algorithm::to_upper_copy()" that + * uses std::string does not work properly. We need to apply it + * one wide strings (std::wstring). This explains the two calls to + * "utf_to_utf" in order to convert to/from std::wstring. + **/ + + std::wstring w = boost::locale::conv::utf_to_utf<wchar_t>(source); + w = boost::algorithm::to_upper_copy<std::wstring>(w, *globalLocale_); + return boost::locale::conv::utf_to_utf<char>(w); + } +#endif + + + void Toolbox::InitializeOpenSsl() + { +#if ORTHANC_ENABLE_SSL == 1 + // https://wiki.openssl.org/index.php/Library_Initialization + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); +#endif + } + + + void Toolbox::FinalizeOpenSsl() + { +#if ORTHANC_ENABLE_SSL == 1 + // Finalize OpenSSL + // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup +#ifdef FIPS_mode_set + FIPS_mode_set(0); +#endif + ENGINE_cleanup(); + CONF_modules_unload(1); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + ERR_free_strings(); +#endif + } + + + std::string Toolbox::GenerateUuid() + { +#ifdef WIN32 + UUID uuid; + UuidCreate ( &uuid ); + + unsigned char * str; + UuidToStringA ( &uuid, &str ); + + std::string s( ( char* ) str ); + + RpcStringFreeA ( &str ); #else - return static_cast<int>(getpid()); + uuid_t uuid; + uuid_generate_random ( uuid ); + char s[37]; + uuid_unparse ( uuid, s ); #endif + return s; } } + + +OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content) +{ + return reinterpret_cast<OrthancLinesIterator*>(new Orthanc::Toolbox::LinesIterator(content)); +} + + +bool OrthancLinesIterator_GetLine(std::string& target, + const OrthancLinesIterator* iterator) +{ + if (iterator != NULL) + { + return reinterpret_cast<const Orthanc::Toolbox::LinesIterator*>(iterator)->GetLine(target); + } + else + { + return false; + } +} + + +void OrthancLinesIterator_Next(OrthancLinesIterator* iterator) +{ + if (iterator != NULL) + { + reinterpret_cast<Orthanc::Toolbox::LinesIterator*>(iterator)->Next(); + } +} + + +void OrthancLinesIterator_Free(OrthancLinesIterator* iterator) +{ + if (iterator != NULL) + { + delete reinterpret_cast<const Orthanc::Toolbox::LinesIterator*>(iterator); + } +}
--- a/Core/Toolbox.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Core/Toolbox.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +40,35 @@ #include <string> #include <json/json.h> + +#if !defined(ORTHANC_ENABLE_BASE64) +# error The macro ORTHANC_ENABLE_BASE64 must be defined +#endif + +#if !defined(ORTHANC_ENABLE_LOCALE) +# error The macro ORTHANC_ENABLE_LOCALE must be defined +#endif + +#if !defined(ORTHANC_ENABLE_MD5) +# error The macro ORTHANC_ENABLE_MD5 must be defined +#endif + +#if !defined(ORTHANC_ENABLE_PUGIXML) +# error The macro ORTHANC_ENABLE_PUGIXML must be defined +#endif + + +/** + * NOTE: GUID vs. UUID + * The simple answer is: no difference, they are the same thing. Treat + * them as a 16 byte (128 bits) value that is used as a unique + * value. In Microsoft-speak they are called GUIDs, but call them + * UUIDs when not using Microsoft-speak. + * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid + **/ + + + namespace Orthanc { typedef std::vector<std::string> UriComponents; @@ -49,10 +79,24 @@ namespace Toolbox { - void ServerBarrier(const bool& stopFlag); + class LinesIterator + { + private: + const std::string& content_; + size_t lineStart_; + size_t lineEnd_; - void ServerBarrier(); + void FindEndOfLine(); + + public: + LinesIterator(const std::string& content); + + bool GetLine(std::string& target) const; + void Next(); + }; + + void ToUpperCase(std::string& s); // Inplace version void ToLowerCase(std::string& s); // Inplace version @@ -63,20 +107,6 @@ void ToLowerCase(std::string& result, const std::string& source); - void ReadFile(std::string& content, - const std::string& path); - - void WriteFile(const std::string& content, - const std::string& path); - - void WriteFile(const void* content, - size_t size, - const std::string& path); - - void USleep(uint64_t microSeconds); - - void RemoveFile(const std::string& path); - void SplitUriComponents(UriComponents& components, const std::string& uri); @@ -92,87 +122,78 @@ std::string FlattenUri(const UriComponents& components, size_t fromLevel = 0); - uint64_t GetFileSize(const std::string& path); - -#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1 +#if ORTHANC_ENABLE_MD5 == 1 void ComputeMD5(std::string& result, const std::string& data); void ComputeMD5(std::string& result, const void* data, - size_t length); + size_t size); #endif void ComputeSHA1(std::string& result, const std::string& data); + void ComputeSHA1(std::string& result, + const void* data, + size_t size); + bool IsSHA1(const char* str, size_t size); bool IsSHA1(const std::string& s); -#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 +#if ORTHANC_ENABLE_BASE64 == 1 void DecodeBase64(std::string& result, const std::string& data); void EncodeBase64(std::string& result, const std::string& data); -# if BOOST_HAS_REGEX == 1 - void DecodeDataUriScheme(std::string& mime, + bool DecodeDataUriScheme(std::string& mime, std::string& content, const std::string& source); -# endif + + void EncodeDataUriScheme(std::string& result, + const std::string& mime, + const std::string& content); #endif - std::string GetPathToExecutable(); - - std::string GetDirectoryOfExecutable(); - +#if ORTHANC_ENABLE_LOCALE == 1 std::string ConvertToUtf8(const std::string& source, Encoding sourceEncoding); std::string ConvertFromUtf8(const std::string& source, Encoding targetEncoding); +#endif + + bool IsAsciiString(const void* data, + size_t size); + + bool IsAsciiString(const std::string& s); std::string ConvertToAscii(const std::string& source); std::string StripSpaces(const std::string& source); -#if BOOST_HAS_DATE_TIME == 1 - std::string GetNowIsoString(); - - void GetNowDicom(std::string& date, - std::string& time); -#endif - // In-place percent-decoding for URL void UrlDecode(std::string& s); Endianness DetectEndianness(); -#if BOOST_HAS_REGEX == 1 std::string WildcardToRegularExpression(const std::string& s); -#endif void TokenizeString(std::vector<std::string>& result, const std::string& source, char separator); - void MakeDirectory(const std::string& path); - - bool IsExistingFile(const std::string& path); - -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 void JsonToXml(std::string& target, const Json::Value& source, const std::string& rootElement = "root", const std::string& arrayElement = "item"); #endif - void ExecuteSystemCommand(const std::string& command, - const std::vector<std::string>& arguments); - bool IsInteger(const std::string& str); void CopyJsonWithoutComments(Json::Value& target, @@ -181,6 +202,62 @@ bool StartsWith(const std::string& str, const std::string& prefix); - int GetProcessId(); + void UriEncode(std::string& target, + const std::string& source); + + std::string GetJsonStringField(const ::Json::Value& json, + const std::string& key, + const std::string& defaultValue); + + bool GetJsonBooleanField(const ::Json::Value& json, + const std::string& key, + bool defaultValue); + + int GetJsonIntegerField(const ::Json::Value& json, + const std::string& key, + int defaultValue); + + unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json, + const std::string& key, + unsigned int defaultValue); + + bool IsUuid(const std::string& str); + + bool StartsWithUuid(const std::string& str); + +#if ORTHANC_ENABLE_LOCALE == 1 + void InitializeGlobalLocale(const char* locale); + + void FinalizeGlobalLocale(); + + std::string ToUpperCaseWithAccents(const std::string& source); +#endif + + void InitializeOpenSsl(); + + void FinalizeOpenSsl(); + + std::string GenerateUuid(); } } + + + + +/** + * The plain C, opaque data structure "OrthancLinesIterator" is a thin + * wrapper around Orthanc::Toolbox::LinesIterator, and is only used by + * "../Resources/WebAssembly/dcdict.cc", in order to avoid code + * duplication + **/ + +struct OrthancLinesIterator; + +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);
--- a/Core/Uuid.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeaders.h" -#include "Uuid.h" - -// http://stackoverflow.com/a/1626302 - -extern "C" -{ -#ifdef WIN32 -#include <rpc.h> -#else -#include <uuid/uuid.h> -#endif -} - -#include <boost/filesystem.hpp> - -namespace Orthanc -{ - namespace Toolbox - { - std::string GenerateUuid() - { -#ifdef WIN32 - UUID uuid; - UuidCreate ( &uuid ); - - unsigned char * str; - UuidToStringA ( &uuid, &str ); - - std::string s( ( char* ) str ); - - RpcStringFreeA ( &str ); -#else - uuid_t uuid; - uuid_generate_random ( uuid ); - char s[37]; - uuid_unparse ( uuid, s ); -#endif - return s; - } - - - bool IsUuid(const std::string& str) - { - if (str.size() != 36) - { - return false; - } - - for (size_t i = 0; i < str.length(); i++) - { - if (i == 8 || i == 13 || i == 18 || i == 23) - { - if (str[i] != '-') - return false; - } - else - { - if (!isalnum(str[i])) - return false; - } - } - - return true; - } - - - bool StartsWithUuid(const std::string& str) - { - if (str.size() < 36) - { - return false; - } - - if (str.size() == 36) - { - return IsUuid(str); - } - - assert(str.size() > 36); - if (!isspace(str[36])) - { - return false; - } - - return IsUuid(str.substr(0, 36)); - } - - - static std::string CreateTemporaryPath(const char* extension) - { -#if BOOST_HAS_FILESYSTEM_V3 == 1 - boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path(); -#elif defined(__linux__) - boost::filesystem::path tmpDir("/tmp"); -#else -#error Support your platform here -#endif - - // We use UUID to create unique path to temporary files - std::string filename = "Orthanc-" + Orthanc::Toolbox::GenerateUuid(); - - if (extension != NULL) - { - filename.append(extension); - } - - tmpDir /= filename; - return tmpDir.string(); - } - - - TemporaryFile::TemporaryFile() : - path_(CreateTemporaryPath(NULL)) - { - } - - - TemporaryFile::TemporaryFile(const char* extension) : - path_(CreateTemporaryPath(extension)) - { - } - - - TemporaryFile::~TemporaryFile() - { - boost::filesystem::remove(path_); - } - } -}
--- a/Core/Uuid.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <string> - -/** - * GUID vs. UUID - * The simple answer is: no difference, they are the same thing. Treat - * them as a 16 byte (128 bits) value that is used as a unique - * value. In Microsoft-speak they are called GUIDs, but call them - * UUIDs when not using Microsoft-speak. - * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid - **/ - -#include "Toolbox.h" - -namespace Orthanc -{ - namespace Toolbox - { - std::string GenerateUuid(); - - bool IsUuid(const std::string& str); - - bool StartsWithUuid(const std::string& str); - - class TemporaryFile - { - private: - std::string path_; - - public: - TemporaryFile(); - - TemporaryFile(const char* extension); - - ~TemporaryFile(); - - const std::string& GetPath() const - { - return path_; - } - - void Write(const std::string& content) - { - Toolbox::WriteFile(content, path_); - } - - void Read(std::string& content) const - { - Toolbox::ReadFile(content, path_); - } - }; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/WebServiceParameters.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,504 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "WebServiceParameters.h" + +#include "Logging.h" +#include "OrthancException.h" +#include "SerializationToolbox.h" +#include "Toolbox.h" + +#if ORTHANC_SANDBOXED == 0 +# include "SystemToolbox.h" +#endif + +#include <cassert> + +namespace Orthanc +{ + static const char* KEY_CERTIFICATE_FILE = "CertificateFile"; + static const char* KEY_CERTIFICATE_KEY_FILE = "CertificateKeyFile"; + static const char* KEY_CERTIFICATE_KEY_PASSWORD = "CertificateKeyPassword"; + static const char* KEY_HTTP_HEADERS = "HttpHeaders"; + static const char* KEY_PASSWORD = "Password"; + static const char* KEY_PKCS11 = "Pkcs11"; + static const char* KEY_URL = "Url"; + static const char* KEY_URL_2 = "URL"; + static const char* KEY_USERNAME = "Username"; + + + static bool IsReservedKey(const std::string& key) + { + return (key == KEY_CERTIFICATE_FILE || + key == KEY_CERTIFICATE_KEY_FILE || + key == KEY_CERTIFICATE_KEY_PASSWORD || + key == KEY_HTTP_HEADERS || + key == KEY_PASSWORD || + key == KEY_PKCS11 || + key == KEY_URL || + key == KEY_URL_2 || + key == KEY_USERNAME); + } + + + WebServiceParameters::WebServiceParameters() : + pkcs11Enabled_(false) + { + SetUrl("http://127.0.0.1:8042/"); + } + + + void WebServiceParameters::ClearClientCertificate() + { + certificateFile_.clear(); + certificateKeyFile_.clear(); + certificateKeyPassword_.clear(); + } + + + void WebServiceParameters::SetUrl(const std::string& url) + { + if (!Toolbox::StartsWith(url, "http://") && + !Toolbox::StartsWith(url, "https://")) + { + LOG(ERROR) << "Bad URL: " << url; + throw OrthancException(ErrorCode_BadFileFormat); + } + + // Add trailing slash if needed + if (url[url.size() - 1] == '/') + { + url_ = url; + } + else + { + url_ = url + '/'; + } + } + + + void WebServiceParameters::ClearCredentials() + { + username_.clear(); + password_.clear(); + } + + + void WebServiceParameters::SetCredentials(const std::string& username, + const std::string& password) + { + if (username.empty() && + !password.empty()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + username_ = username; + password_ = password; + } + } + + + void WebServiceParameters::SetClientCertificate(const std::string& certificateFile, + const std::string& certificateKeyFile, + const std::string& certificateKeyPassword) + { + if (certificateFile.empty()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (certificateKeyPassword.empty()) + { + LOG(ERROR) << "The password for the HTTPS certificate is not provided: " << certificateFile; + throw OrthancException(ErrorCode_BadFileFormat); + } + + certificateFile_ = certificateFile; + certificateKeyFile_ = certificateKeyFile; + certificateKeyPassword_ = certificateKeyPassword; + } + + + void WebServiceParameters::FromSimpleFormat(const Json::Value& peer) + { + assert(peer.isArray()); + + pkcs11Enabled_ = false; + ClearClientCertificate(); + + if (peer.size() != 1 && + peer.size() != 3) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + SetUrl(peer.get(0u, "").asString()); + + if (peer.size() == 1) + { + ClearCredentials(); + } + else if (peer.size() == 2) + { + LOG(ERROR) << "The HTTP password is not provided"; + throw OrthancException(ErrorCode_BadFileFormat); + } + else if (peer.size() == 3) + { + SetCredentials(peer.get(1u, "").asString(), + peer.get(2u, "").asString()); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + static std::string GetStringMember(const Json::Value& peer, + const std::string& key, + const std::string& defaultValue) + { + if (!peer.isMember(key)) + { + return defaultValue; + } + else if (peer[key].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return peer[key].asString(); + } + } + + + void WebServiceParameters::FromAdvancedFormat(const Json::Value& peer) + { + assert(peer.isObject()); + + std::string url = GetStringMember(peer, KEY_URL, ""); + if (url.empty()) + { + SetUrl(GetStringMember(peer, KEY_URL_2, "")); + } + else + { + SetUrl(url); + } + + SetCredentials(GetStringMember(peer, KEY_USERNAME, ""), + GetStringMember(peer, KEY_PASSWORD, "")); + + std::string file = GetStringMember(peer, KEY_CERTIFICATE_FILE, ""); + if (!file.empty()) + { + SetClientCertificate(file, GetStringMember(peer, KEY_CERTIFICATE_KEY_FILE, ""), + GetStringMember(peer, KEY_CERTIFICATE_KEY_PASSWORD, "")); + } + else + { + ClearClientCertificate(); + } + + if (peer.isMember(KEY_PKCS11)) + { + if (peer[KEY_PKCS11].type() == Json::booleanValue) + { + pkcs11Enabled_ = peer[KEY_PKCS11].asBool(); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + else + { + pkcs11Enabled_ = false; + } + + + headers_.clear(); + + if (peer.isMember(KEY_HTTP_HEADERS)) + { + const Json::Value& h = peer[KEY_HTTP_HEADERS]; + if (h.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + Json::Value::Members keys = h.getMemberNames(); + for (size_t i = 0; i < keys.size(); i++) + { + const Json::Value& value = h[keys[i]]; + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + headers_[keys[i]] = value.asString(); + } + } + } + } + + + userProperties_.clear(); + + const Json::Value::Members members = peer.getMemberNames(); + + for (Json::Value::Members::const_iterator it = members.begin(); + it != members.end(); ++it) + { + if (!IsReservedKey(*it)) + { + if (peer[*it].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + userProperties_[*it] = peer[*it].asString(); + } + } + } + } + + + void WebServiceParameters::Unserialize(const Json::Value& peer) + { + try + { + if (peer.isArray()) + { + FromSimpleFormat(peer); + } + else if (peer.isObject()) + { + FromAdvancedFormat(peer); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + catch (OrthancException&) + { + throw; + } + catch (...) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void WebServiceParameters::ListHttpHeaders(std::set<std::string>& target) const + { + target.clear(); + + for (Dictionary::const_iterator it = headers_.begin(); + it != headers_.end(); ++it) + { + target.insert(it->first); + } + } + + + bool WebServiceParameters::LookupHttpHeader(std::string& value, + const std::string& key) const + { + Dictionary::const_iterator found = headers_.find(key); + + if (found == headers_.end()) + { + return false; + } + else + { + value = found->second; + return true; + } + } + + + void WebServiceParameters::AddUserProperty(const std::string& key, + const std::string& value) + { + if (IsReservedKey(key)) + { + LOG(ERROR) << "Cannot use this reserved key to name an user property: " << key; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + userProperties_[key] = value; + } + } + + + void WebServiceParameters::ListUserProperties(std::set<std::string>& target) const + { + target.clear(); + + for (Dictionary::const_iterator it = userProperties_.begin(); + it != userProperties_.end(); ++it) + { + target.insert(it->first); + } + } + + + bool WebServiceParameters::LookupUserProperty(std::string& value, + const std::string& key) const + { + Dictionary::const_iterator found = userProperties_.find(key); + + if (found == userProperties_.end()) + { + return false; + } + else + { + value = found->second; + return true; + } + } + + + bool WebServiceParameters::IsAdvancedFormatNeeded() const + { + return (!certificateFile_.empty() || + !certificateKeyFile_.empty() || + !certificateKeyPassword_.empty() || + pkcs11Enabled_ || + !headers_.empty() || + !userProperties_.empty()); + } + + + void WebServiceParameters::Serialize(Json::Value& value, + bool forceAdvancedFormat, + bool includePasswords) const + { + if (forceAdvancedFormat || + IsAdvancedFormatNeeded()) + { + value = Json::objectValue; + value[KEY_URL] = url_; + + if (!username_.empty() || + !password_.empty()) + { + value[KEY_USERNAME] = username_; + + if (includePasswords) + { + value[KEY_PASSWORD] = password_; + } + } + + if (!certificateFile_.empty()) + { + value[KEY_CERTIFICATE_FILE] = certificateFile_; + } + + if (!certificateKeyFile_.empty()) + { + value[KEY_CERTIFICATE_KEY_FILE] = certificateKeyFile_; + } + + if (!certificateKeyPassword_.empty() && + includePasswords) + { + value[KEY_CERTIFICATE_KEY_PASSWORD] = certificateKeyPassword_; + } + + value[KEY_PKCS11] = pkcs11Enabled_; + + value[KEY_HTTP_HEADERS] = Json::objectValue; + for (Dictionary::const_iterator it = headers_.begin(); + it != headers_.end(); ++it) + { + value[KEY_HTTP_HEADERS][it->first] = it->second; + } + + for (Dictionary::const_iterator it = userProperties_.begin(); + it != userProperties_.end(); ++it) + { + value[it->first] = it->second; + } + } + else + { + value = Json::arrayValue; + value.append(url_); + + if (!username_.empty() || + !password_.empty()) + { + value.append(username_); + value.append(includePasswords ? password_ : ""); + } + } + } + + +#if ORTHANC_SANDBOXED == 0 + void WebServiceParameters::CheckClientCertificate() const + { + if (!certificateFile_.empty()) + { + if (!SystemToolbox::IsRegularFile(certificateFile_)) + { + LOG(ERROR) << "Cannot open certificate file: " << certificateFile_; + throw OrthancException(ErrorCode_InexistentFile); + } + + if (!certificateKeyFile_.empty() && + !SystemToolbox::IsRegularFile(certificateKeyFile_)) + { + LOG(ERROR) << "Cannot open key file: " << certificateKeyFile_; + throw OrthancException(ErrorCode_InexistentFile); + } + } + } +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/WebServiceParameters.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,179 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#include <map> +#include <set> +#include <string> +#include <json/json.h> + +namespace Orthanc +{ + class WebServiceParameters + { + public: + typedef std::map<std::string, std::string> Dictionary; + + private: + std::string url_; + std::string username_; + std::string password_; + std::string certificateFile_; + std::string certificateKeyFile_; + std::string certificateKeyPassword_; + bool pkcs11Enabled_; + Dictionary headers_; + Dictionary userProperties_; + + void FromSimpleFormat(const Json::Value& peer); + + void FromAdvancedFormat(const Json::Value& peer); + + public: + WebServiceParameters(); + + WebServiceParameters(const Json::Value& serialized) + { + Unserialize(serialized); + } + + const std::string& GetUrl() const + { + return url_; + } + + void SetUrl(const std::string& url); + + void ClearCredentials(); + + void SetCredentials(const std::string& username, + const std::string& password); + + const std::string& GetUsername() const + { + return username_; + } + + const std::string& GetPassword() const + { + return password_; + } + + void ClearClientCertificate(); + + void SetClientCertificate(const std::string& certificateFile, + const std::string& certificateKeyFile, + const std::string& certificateKeyPassword); + + const std::string& GetCertificateFile() const + { + return certificateFile_; + } + + const std::string& GetCertificateKeyFile() const + { + return certificateKeyFile_; + } + + const std::string& GetCertificateKeyPassword() const + { + return certificateKeyPassword_; + } + + void SetPkcs11Enabled(bool enabled) + { + pkcs11Enabled_ = enabled; + } + + bool IsPkcs11Enabled() const + { + return pkcs11Enabled_; + } + + void AddHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + void ClearHttpHeaders() + { + headers_.clear(); + } + + const Dictionary& GetHttpHeaders() const + { + return headers_; + } + + void ListHttpHeaders(std::set<std::string>& target) const; + + bool LookupHttpHeader(std::string& value, + const std::string& key) const; + + void AddUserProperty(const std::string& key, + const std::string& value); + + void ClearUserProperties() + { + userProperties_.clear(); + } + + const Dictionary& GetUserProperties() const + { + return userProperties_; + } + + void ListUserProperties(std::set<std::string>& target) const; + + bool LookupUserProperty(std::string& value, + const std::string& key) const; + + bool IsAdvancedFormatNeeded() const; + + void Unserialize(const Json::Value& peer); + + void Serialize(Json::Value& value, + bool forceAdvancedFormat, + bool includePasswords) const; + +#if ORTHANC_SANDBOXED == 0 + void CheckClientCertificate() const; +#endif + }; +}
--- a/DarwinCompilation.txt Thu Oct 29 11:25:45 2015 +0100 +++ b/DarwinCompilation.txt Fri Oct 12 15:18:10 2018 +0200 @@ -31,7 +31,9 @@ # cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON ~/Orthanc NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to -your version of XCode. +your version of OS X. This version can obtained by typing: + +# sw_vers Build the Debug version of Orthanc @@ -47,6 +49,6 @@ ------------------------------------ # xcodebuild -configuration Release -# ./Debug/UnitTests +# ./Release/UnitTests The binaries of Orthanc are located at "~/OrthancBuild/Release/Orthanc".
--- a/INSTALL Thu Oct 29 11:25:45 2015 +0100 +++ b/INSTALL Fri Oct 12 15:18:10 2018 +0200 @@ -51,14 +51,19 @@ third-party dependencies, delete the "~/Orthanc/ThirdPartyDownloads/" folder, then restart cmake. +WARNING 3: If performance is important to you, make sure to add the +option "-DCMAKE_BUILD_TYPE=Release" when invoking cmake. Indeed, by +default, run-time debug assertions are enabled, which can seriously +impact performance, especially if your Orthanc server stores a lot of +DICOM instances. -Native Linux Compilation ------------------------- + +Native GNU/Linux Compilation +---------------------------- See the file "LinuxCompilation.txt". - Native OS X Compilation ----------------------- @@ -70,36 +75,55 @@ ------------------------------------------------- # cd [...]\OrthancBuild -# cmake -DSTANDALONE_BUILD=ON -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON -G "Visual Studio 8 2005" [...]\Orthanc +# cmake -DSTANDALONE_BUILD=ON -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON -DUSE_LEGACY_JSONCPP=ON -G "Visual Studio 9 2008" [...]\Orthanc Then open the "[...]/OrthancBuild/Orthanc.sln" with Visual Studio. NOTES: -* More recent versions of Visual Studio than 2005 should also +* More recent versions of Visual Studio than 2008 should also work. Type "cmake" without arguments to have the list of generators that are available on your computer. * You will have to install the Platform SDK (version 6 or above) for Visual Studio 2005: http://en.wikipedia.org/wiki/Microsoft_Windows_SDK. Read the CMake FAQ: http://goo.gl/By90B +* The "-DUSE_LEGACY_JSONCPP=ON" must be set for versions of + Visual Studio that do not support C++11 -Cross-Compilation for Windows under Linux ------------------------------------------ +Cross-Compilation for Windows under GNU/Linux +--------------------------------------------- -To cross-compile Windows binaries under Linux using MinGW, please use -the following command: +Some versions of MinGW-W64 might have problems with C++11 (notably +those shipped in Ubuntu 16.04 LTS, in the "mingw-w64" package). Use +the following command to disable C++11: # cd ~/OrthancBuild -# cmake -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc +# cmake ~/Orthanc \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGW-W64-Toolchain32.cmake \ + -DSTANDALONE_BUILD=ON \ + -DSTATIC_BUILD=ON \ + -DUSE_LEGACY_JSONCPP=ON # make +NB: Use the toolchain "MinGW-W64-Toolchain64.cmake" to produce 64bit +Windows binaries. + -Native Windows build with MinGW (VERY SLOW) -------------------------------------------- +Legacy MinGW32 compilers (notably those shipped in Ubuntu 14.04 LTS, +in the "mingw32" package) are incompatible with DCMTK 3.6.2 and +C++11. Use the following command to force using DCMTK 3.6.0 and +disable C++11: -# cd [...]\OrthancBuild -# cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug [...]\Orthanc -# mingw32-make +# cd ~/OrthancBuild +# cmake ~/Orthanc \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake \ + -DSTANDALONE_BUILD=ON \ + -DSTATIC_BUILD=ON \ + -DUSE_DCMTK_360=ON \ + -DUSE_LEGACY_JSONCPP=ON +# make
--- a/LinuxCompilation.txt Thu Oct 29 11:25:45 2015 +0100 +++ b/LinuxCompilation.txt Fri Oct 12 15:18:10 2018 +0200 @@ -1,17 +1,17 @@ This file is a complement to "INSTALL", which contains instructions -that are specific to Linux. +that are specific to GNU/Linux. -Static linking for Linux -======================== +Static linking for GNU/Linux +============================ -The most simple way of building Orthanc under Linux consists in +The most simple way of building Orthanc under GNU/Linux consists in statically linking against all the third-party dependencies. In this case, the system-wide libraries will not be used. The build tool (CMake) will download the sources of all the required packages and automatically compile them. -This process should work on any Linux distribution, provided that a +This process should work on any GNU/Linux distribution, provided that a C/C++ compiler ("build-essential" in Debian-based systems), the Python interpreter, CMake, the "unzip" system tool, and the development package for libuuid ("uuid-dev" in Debian) are installed. @@ -47,14 +47,14 @@ Note 3- To build the documentation, you will have to install doxyen. -Use system-wide libraries under Linux -===================================== +Use system-wide libraries under GNU/Linux +========================================= -Under Linux, by default, Orthanc links against the shared libraries of -your system (the "STATIC_BUILD" option is set to "OFF"). This greatly -speeds up the compilation. This is also required when building -packages for Linux distributions. Because using system libraries is -the default behavior, you just have to use: +Under GNU/Linux, by default, Orthanc links against the shared +libraries of your system (the "STATIC_BUILD" option is set to +"OFF"). This greatly speeds up the compilation. This is also required +when building packages for GNU/Linux distributions. Because using +system libraries is the default behavior, you just have to use: # cd ~/OrthancBuild # cmake -DCMAKE_BUILD_TYPE=Debug ~/Orthanc @@ -62,13 +62,14 @@ Note that to build the documentation, you will have to install doxyen. -However, on some Linux distributions, it is still required to download -and static link against some third-party dependencies, e.g. when the -system-wide library is not shipped or is outdated. Because of -difference in the packaging of the various Linux distribution, it is -also sometimes required to fine-tune some options. +However, on some GNU/Linux distributions, it is still required to +download and static link against some third-party dependencies, +e.g. when the system-wide library is not shipped or is +outdated. Because of difference in the packaging of the various +GNU/Linux distribution, it is also sometimes required to fine-tune +some options. -You will find below build instructions for specific Linux +You will find below build instructions for specific GNU/Linux distributions. Distributions tagged by "SUPPORTED" are tested by Sébastien Jodogne. Distributions tagged by "CONTRIBUTED" come from Orthanc users. @@ -79,14 +80,15 @@ # sudo apt-get install build-essential unzip cmake mercurial \ uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ - libgoogle-glog-dev libgtest-dev libpng-dev libjpeg-dev \ + libgtest-dev libpng-dev libjpeg-dev \ libsqlite3-dev libssl-dev zlib1g-dev libdcmtk2-dev \ libboost-all-dev libwrap0-dev libjsoncpp-dev libpugixml-dev # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ -DDCMTK_LIBRARIES=dcmjpls \ + -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc Note: Have also a look at the official package: @@ -106,9 +108,9 @@ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ - -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_SYSTEM_PUGIXML=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ + -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc @@ -123,9 +125,10 @@ libcharls-dev libjsoncpp-dev libpugixml-dev # cmake -DALLOW_DOWNLOADS=ON \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DDCMTK_LIBRARIES=dcmjpls \ + -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc @@ -134,7 +137,7 @@ ------------------------ # sudo yum install unzip make automake gcc gcc-c++ python cmake \ - boost-devel curl-devel dcmtk-devel glog-devel \ + boost-devel curl-devel dcmtk-devel \ gtest-devel libpng-devel libsqlite3x-devel libuuid-devel jpeg-devel \ mongoose-devel openssl-devel jsoncpp-devel lua-devel pugixml-devel @@ -144,6 +147,7 @@ # cmake "-DDCMTK_LIBRARIES=CharLS" \ -DSYSTEM_MONGOOSE_USE_CALLBACKS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc Note: Have also a look at the official package: @@ -155,11 +159,12 @@ ------------------------ # pkg install jsoncpp pugixml lua51 curl googletest dcmtk cmake jpeg \ - e2fsprogs-libuuid glog boost-libs sqlite3 python libiconv + e2fsprogs-libuuid boost-libs sqlite3 python libiconv # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DDCMTK_LIBRARIES="dcmdsig;charls;dcmjpls" \ + -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc @@ -180,12 +185,13 @@ -DUSE_SYSTEM_DCMTK=OFF \ -DUSE_SYSTEM_GOOGLE_TEST=OFF \ -DUSE_SYSTEM_LIBJPEG=OFF \ + -DCMAKE_BUILD_TYPE=Release \ ~/Orthanc -Other Linux distributions? --------------------------- +Other GNU/Linux distributions? +------------------------------ Please send us your build instructions (by a mail to s.jodogne@gmail.com)! @@ -203,7 +209,7 @@ Using ccache ============ -Under Linux, you also have the opportunity to use "ccache" to +Under GNU/Linux, you also have the opportunity to use "ccache" to dramatically decrease the compilation time when rebuilding Orthanc. This is especially useful for developers. To this end, you would use:
--- a/NEWS Thu Oct 29 11:25:45 2015 +0100 +++ b/NEWS Fri Oct 12 15:18:10 2018 +0200 @@ -1,21 +1,492 @@ Pending changes in the mainline =============================== -* Full indexation of the patient/study tags to speed up searches and C-Find -* Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive") -* "/tools/create-dicom": Support of binary tags encoded using data URI scheme -* "/tools/create-dicom": Support of hierarchical structures (creation of sequences) -* "/modify" can insert/modify sequences -* "/series/.../ordered-slices" to order the slices of a 2D+t or 3D image -* New URI "/tools/shutdown" to stop Orthanc from the REST API -* New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed" + +General +------- + +* Possibility to restrict the allowed DICOM commands for each modality + +Orthanc Explorer +---------------- + +* The first screen of Orthanc Explorer is now a form to do studies lookups +* Support of large databases, by limiting the results to 100 patients or studies + +REST API +-------- + +* New URI: "/studies/.../merge" to merge a study +* New URI: "/studies/.../split" to split a study + +Maintenance +----------- + +* Executing a query/retrieve from the REST API now creates a job +* Fix: Closing DICOM associations after running query/retrieve from REST API + + +Version 1.4.2 (2018-09-20) +========================== + +General +------- + +* "OrthancPeers" configuration option now allows to specify HTTP headers +* New main DICOM tag: "ImageOrientationPatient" at the instance level +* New configuration options: + - "HttpVerbose" to debug outgoing HTTP connections + - "OverwriteInstances" to choose how duplicate SOPInstanceUID are handled + +Orthanc Explorer +---------------- + +* Query/retrieve: Added button for "DX" modality + +REST API +-------- + +* "/tools/reconstruct" to reconstruct the main DICOM tags, the JSON summary and + the metadata of all the instances stored in Orthanc. This is a slow operation! + +Plugins +------- + +* New primitives to access Orthanc peers from plugins +* New events in change callbacks: "UpdatedPeers" and "UpdatedModalities" +* New primitives to handle jobs from plugins: "OrthancPluginSubmitJob()" + and "OrthancPluginRegisterJobsUnserializer()" + +Lua +--- + +* IncomingWorklistRequestFilter() to filter incoming C-FIND worklist queries + +Maintenance +----------- + +* Fix "/series/.../ordered-slices" in the presence of non-parallel slices +* Fix incoming DICOM C-Store filtering for JPEG-LS transfer syntaxes +* Fix OrthancPluginHttpClient() to return the HTTP status on errors +* Fix HTTPS requests to sites using a certificate encrypted with ECDSA +* Fix handling of incoming C-FIND queries containing Generic Group Length (*, 0x0000) +* Fix issue 54 (quoting multipart answers), for OsiriX compatibility through DICOMweb +* Fix issue 98 (DCMTK configuration fails with GCC 6.4.0 on Alpine) +* Fix issue 99 (PamWriter test segfaults on alpine linux with gcc 6.4.0) + + +Version 1.4.1 (2018-07-17) +========================== + +* Fix deadlock in Lua scripting +* Simplification to the "DatabaseWrapper" class + + +Version 1.4.0 (2018-07-13) +========================== + +General +------- + +* New advanced job engine +* New configuration options: + - "ConcurrentJobs": Max number of jobs that are simultaneously running + - "SynchronousCMove": Whether to run DICOM C-Move operations synchronously + - "JobsHistorySize": Max number of completed jobs that are kept in memory +* New metadata automatically computed at the instance level: + "RemoteIp", "CalledAet" and "HttpUsername" + +Orthanc Explorer +---------------- + +* New screen listing all the available studies + +REST API +-------- + +* "/jobs/..." to manage the jobs from the REST API +* New option "?short" to list DICOM tags using their hexadecimal ID in: + - "/instances/.../tags?short" + - "/instances/.../header?short" + - "/{patients|studies|series}/.../instances-tags?short" + - "/{patients|studies|series}/.../shared-tags?short" + - "/{patients|studies|series|instances}/.../module?short" + - "/studies/.../module-patient?short" +* "/instances/.../tags" URI was returning only the first value of + DicomTags containing multiple numerical value. It now returns all + values in a string separated by \\ (i.e.: "1\\2\\3"). Note that, + for data already in Orthanc, you'll need to reconstruct the data by + sending a POST request to the ".../reconstruct" URI. This change + triggered an update of ORTHANC_API_VERSION from 1.0 to 1.1 +* "/instances/.../frame/../image-uint8 and friends now accepts a + "image/pam" MIME type to retrieve images in PAM format + (https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format) +* New option "?expand" to "/instances/.../metadata" + +Plugins +------- + +* New primitive in database SDK: "lookupIdentifierRange" to speed up range searches +* New function in the SDK: "OrthancPluginCheckVersionAdvanced()" + +Maintenance +----------- + +* Configuration option "LogExportedResources" is now "false" by default +* Header "OrthancCppDatabasePlugin.h" is now part of the "orthanc-databases" project +* Fix generation of DICOMDIR if PatientID is empty +* Fix issue 25 (Deadlock with Lua scripts): The event queue is now implemented for Lua +* Fix issue 94 (Instance modification should not modify FrameOfReferenceUID) +* Fix issue 77 (Lua access to REST-API is null terminated) +* Fix memory leak introduced by changeset #99116ed6f38c in Orthanc 1.3.2 +* Upgraded dependencies for static and Windows builds: + - boost 1.67.0 + - openssl 1.0.2o + + +Version 1.3.2 (2018-04-18) +========================== + +REST API +-------- + +* "/system" URI returns the version of the Orthanc REST API +* "/tools/now" returns the current UTC (universal) time +* "/tools/now-local" returns the curent local time. + This was the behavior of "/tools/now" until release 1.3.1. +* Added "?expand" GET argument to "/peers" and "/modalities" routes +* New URI: "/tools/create-media-extended" to generate a DICOMDIR + archive from several resources, including additional type-3 tags +* Preservation of UID relationships while anonymizing + +Lua +--- + +* New CMake option: "-DENABLE_LUA_MODULES=ON" to enable support for + loading external Lua modules if the Lua engine is statically linked + +Plugins +------- + +* New error code: DatabaseUnavailable + +Maintenance +----------- + +* Orthanc now uses UTC (universal time) instead of local time in its database +* Fix to allow creating DICOM instances with empty Specific Character Set (0008,0005) +* Support of Linux Standard Base +* Static linking against libuuid (from e2fsprogs) +* Fix static build on CentOS 6 +* Possibility of using JsonCpp 0.10.6 if the compiler does not support C++11 + with the "-DUSE_LEGACY_JSONCPP=ON" CMake option +* Upgraded dependencies for static and Windows builds: + - boost 1.66.0 + - curl 7.57.0 + - jsoncpp 1.8.4 + - zlib 1.2.11 + + +Version 1.3.1 (2017-11-29) +========================== + +General +------- + +* Built-in decoding of palette images + +REST API +-------- + +* New URI: "/instances/.../frames/.../raw.gz" to compress raw frames using gzip +* New argument "ignore-length" to force the inclusion of too long tags in JSON +* New argument "/.../media?extended" to include additional type-3 tags in DICOMDIR Plugins ------- -* New function "OrthancPluginRegisterErrorCode()" to declare custom error codes -* New function "OrthancPluginRegisterDictionaryTag()" to declare DICOM tags -* New "OrthancStarted" and "OrthancStopped" events in change callbacks +* New pixel formats exposed in SDK: BGRA32, Float32, Grayscale32, RGB48 + +Maintenance +----------- + +* Creation of ./Resources/CMake/OrthancFramework*.cmake to reuse the Orthanc + C++ framework in other projects +* New security-related options: "DicomAlwaysAllowEcho" +* Use "GBK" (frequently used in China) as an alias for "GB18030" +* Experimental support of actively maintained Civetweb to replace Mongoose 3.8 +* Fix issue 31 for good (create new modality types for Philips ADW, GE Xeleris, GE AWServer) +* Fix issue 64 (OpenBSD support) +* Fix static compilation of DCMTK 3.6.2 on Fedora +* Upgrade to Boost 1.65.1 in static builds +* Upgrade to SQLite amalgamation 3.21.0 in static builds + + +Version 1.3.0 (2017-07-19) +========================== + +General +------- + +* Orthanc now anonymizes according to Basic Profile of PS 3.15-2017c Table E.1-1 +* In the "DicomModalities" configuration: + - Manufacturer type MedInria is now obsolete + - Manufacturer types AgfaImpax and SyngoVia are obsolete too + (use GenericNoWildcardInDates instead) + - Obsolete manufacturers are still accepted but might disappear in the future + - Added new manufacturer: GenericNoUniversalWildcard to replace all '*' by '' in + outgoing C-Find requests +* New security-related options: "DicomAlwaysAllowStore" and "DicomCheckModalityHost" + +REST API +-------- + +* Argument "Since" in URI "/tools/find" (related to issue 53) +* Argument "DicomVersion" in URIs "/{...}/{...}/anonymization" + +Plugins +------- + +* New function: "OrthancPluginRegisterIncomingHttpRequestFilter2()" + +Lua +--- + +* Added HTTP headers support for Lua HttpPost/HttpGet/HttpPut/HttpDelete + +Orthanc Explorer +---------------- + +* Query/retrieve: Added button for "DR" modality + +Maintenance +----------- + +* Ability to retrieve raw frames encoded as unsigned 32-bits integers +* Fix issue 29 (more consistent handling of the "--upgrade" argument) +* Fix issue 31 (create new modality types for Philips ADW, GE Xeleris, GE AWServer) +* Fix issue 35 (AET name is not transferred to Orthanc using DCMTK 3.6.0) +* Fix issue 44 (bad interpretation of photometric interpretation MONOCHROME1) +* Fix issue 45 (crash when providing a folder to "--config" command-line option) +* Fix issue 46 (PHI remaining after anonymization) +* Fix issue 49 (worklists: accentuated characters are removed from C-Find responses) +* Fix issue 52 (DICOM level security association problems) +* Fix issue 55 (modification/anonymization of tags that might break the database + model now requires the "Force" parameter to be set to "true" in the query) +* Fix issue 56 (case-insensitive matching over accents) +* Fix Debian #865606 (orthanc FTBFS with libdcmtk-dev 3.6.1~20170228-2) +* Fix XSS inside DICOM in Orthanc Explorer (as reported by Victor Pasnkel, Morphus Labs) +* Upgrade to DCMTK 3.6.2 in static builds (released on 2017-07-17) +* Upgrade to Boost 1.64.0 in static builds +* New advanced "Locale" configuration option +* Removed configuration option "USE_DCMTK_361_PRIVATE_DIC" + + +Version 1.2.0 (2016/12/13) +========================== + +General +------- + +* Handling of private tags/creators in the "Dictionary" configuration option +* New configuration options: "LoadPrivateDictionary", "DicomScuTimeout" and "DicomScpTimeout" +* New metadata automatically computed at the instance level: "TransferSyntax" and "SopClassUid" + +REST API +-------- + +* "/tools/invalidate-tags" to invalidate the JSON summary of all the DICOM files + (useful if private tags are registered, or if changing the default encoding) +* "Permissive" flag for URI "/modalities/{...}/store" to ignore C-STORE transfer errors +* "Asynchronous" flag for URIs "/modalities/{...}/store" and "/peers/{...}/store" + to avoid waiting for the completion of image transfers +* Possibility to DELETE "dicom-as-json" attachments to reconstruct the JSON summaries + (useful if "Dictionary" has changed) +* "Keep" option for modifications to keep original DICOM identifiers (advanced feature) +* "/tools/default-encoding" to get or temporarily change the default encoding +* "/{resource}/{id}/reconstruct" to reconstruct the main DICOM tags, the JSON summary and + the metadata of a resource (useful to compute new metadata, or if using "Keep" above) + +Plugins +------- + +* New function: "OrthancPluginRegisterPrivateDictionaryTag()" to register private tags +* More control over client cache in the ServeFolders plugin +* New C++ help wrappers in "Plugins/Samples/Common/" to read DICOM datasets from REST API +* New data structure: "OrthancPluginFindMatcher" to match DICOM against C-FIND queries + +Maintenance +----------- + +* Fix handling of encodings in C-FIND requests (including for worklists) +* Use of DCMTK 3.6.1 dictionary of private tags in standalone builds +* Avoid hard crash if not enough memory (handling of std::bad_alloc) +* Improved robustness of Orthanc Explorer wrt. query/retrieve (maybe fix issue 24) +* Fix serious performance issue with C-FIND +* Fix extraction of the symbolic name of the private tags +* Performance warning if runtime debug assertions are turned on +* Improved robustness against files with no PatientID +* Upgrade to curl 7.50.3 for static and Windows builds +* Content-Type for JSON documents is now "application/json; charset=utf-8" +* Ignore "Group Length" tags in C-FIND queries +* Fix handling of worklist SCP with ReferencedStudySequence and ReferencedPatientSequence +* Fix handling of Move Originator AET and ID in C-MOVE SCP +* Fix vulnerability ZSL-2016-5379 "Unquoted Service Path Privilege Escalation" in the + Windows service +* Fix vulnerability ZSL-2016-5380 "Remote Memory Corruption Vulnerability" in DCMTK 3.6.0 + + +Version 1.1.0 (2016/06/27) +========================== + +General +------- + +* HTTPS client certificates can be associated with Orthanc peers to enhance security over Internet +* Possibility to use PKCS#11 authentication for hardware security modules with Orthanc peers +* New command-line option "--logfile" to output the Orthanc log to the given file +* Support of SIGHUP signal (restart Orthanc only if the configuration files have changed) + +REST API +-------- + +* New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding) +* New URI "/modalities/.../move" to issue C-MOVE SCU requests +* "MoveOriginatorID" can be specified for "/modalities/.../store" + +Dicom protocol +-------------- + +* Support of optional tags for counting resources in C-FIND: + 0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209 +* Support of Move Originator Message ID (0000,1031) in C-STORE responses driven by C-MOVE + +Plugins +------- + +* Speedup in plugins by removing unnecessary locks +* New callback to filter incoming HTTP requests: OrthancPluginRegisterIncomingHttpRequestFilter() +* New callback to handle non-worklists C-FIND requests: OrthancPluginRegisterFindCallback() +* New callback to handle C-MOVE requests: OrthancPluginRegisterMoveCallback() +* New function: "OrthancPluginHttpClient()" to do HTTP requests with full control +* New function: "OrthancPluginGenerateUuid()" to generate a UUID +* More than one custom image decoder can be installed (e.g. to handle different transfer syntaxes) + +Lua +--- + +* Possibility to dynamically fix outgoing C-FIND requests using "OutgoingFindRequestFilter()" +* Access to the HTTP headers in the "IncomingHttpRequestFilter()" callback + +Image decoding +-------------- + +* Huge speedup if decoding the family of JPEG transfer syntaxes +* Refactoring leading to speedups with custom image decoders (including Web viewer plugin) +* Support decoding of RLE Lossless transfer syntax +* Support of signed 16bpp images in ParsedDicomFile + +Maintenance +----------- + +* New logo of Orthanc +* Fix issue 11 (is_regular_file() fails for FILE_ATTRIBUTE_REPARSE_POINT) +* Fix issue 16 ("Limit" parameter error in REST API /tools/find method) +* Fix of Debian bug #818512 ("FTBFS: Please install libdcmtk*-dev") +* Fix of Debian bug #823139 ("orthanc: Please provide RecoverCompressedFile.cpp") +* Replaced "localhost" by "127.0.0.1", as it might impact performance on Windows +* Compatibility with CMake >= 3.5.0 +* Possibility to use forthcoming DCMTK 3.6.1 in static builds (instead of 3.6.0) +* Upgrade to Boost 1.60.0 for static builds +* Use of HTTP status 403 Forbidden (instead of 401) if access to a REST resource is disallowed +* Option "HttpsVerifyPeers" can be used to connect against self-signed HTTPS certificates +* New configuration option "AllowFindSopClassesInStudy" +* Macro "__linux" (now obsolete) replaced by macro "__linux__" (maybe solves Debian bug #821011) +* Modification of instances can now replace PixelData (resp. EncapsulatedDocument) with + provided a PNG/JPEG image (resp. PDF file) if it is encoded using Data URI Scheme +* Dropped support of Google Log + + +Version 1.0.0 (2015/12/15) +========================== + +* Lua: "IncomingFindRequestFilter()" to apply filters to incoming C-FIND requests +* New function in plugin SDK: "OrthancPluginSendMultipartItem2()" +* Fix of DICOMDIR generation with DCMTK 3.6.1, support of encodings +* Fix range search if the lower or upper limit is absent +* Fix modality worklists lookups if tags with UN (unknown) VR are present +* Warn about badly formatted modality/peer definitions in configuration file at startup + + +Version 0.9.6 (2015/12/08) +========================== + +* Promiscuous mode (accept unknown SOP class UID) is now turned off by default +* Fix serialization of DICOM buffers that might contain garbage trailing +* Fix modality worklists server if some fields are null +* More tolerant "/series/.../ordered-slices" with broken series +* Improved logging information if upgrade fails +* Fix formatting of multipart HTTP answers (bis) + + +Version 0.9.5 (2015/12/02) +========================== + +Major +----- + +* Experimental support of DICOM C-FIND SCP for modality worklists through plugins +* Support of DICOM C-FIND SCU for modality worklists ("/modalities/{dicom}/find-worklist") + +REST API +-------- + +* New URIs: + - "/series/.../ordered-slices" to order the slices of a 2D+t or 3D series + - "/tools/shutdown" to stop Orthanc from the REST API + - ".../compress", ".../uncompress" and ".../is-compressed" for attachments + - "/tools/create-archive" to create ZIP from a set of resources + - "/tools/create-media" to create ZIP+DICOMDIR from a set of resources + - "/instances/.../header" to get the meta information (header) of the DICOM instance +* "/tools/create-dicom": + - Support of binary tags encoded using data URI scheme + - Support of hierarchical structures (creation of sequences) + - Create tags with unknown VR +* "/modify" can insert/modify sequences +* ".../preview" and ".../image-uint8" can return JPEG images if the HTTP Accept Header asks so +* "Origin" metadata for the instances + +Minor +----- + +* New configuration options: + - "UnknownSopClassAccepted" to disable promiscuous mode (accept unknown SOP class UID) + - New configuration option: "Dictionary" to declare custom DICOM tags +* Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive") +* MIME content type can be associated to custom attachments (cf. "UserContentType") + +Plugins +------- + +* New functions: + - "OrthancPluginRegisterDecodeImageCallback()" to replace the built-in image decoder + - "OrthancPluginDicomInstanceToJson()" to convert DICOM to JSON + - "OrthancPluginDicomBufferToJson()" to convert DICOM to JSON + - "OrthancPluginRegisterErrorCode()" to declare custom error codes + - "OrthancPluginRegisterDictionaryTag()" to declare custom DICOM tags + - "OrthancPluginLookupDictionary()" to get information about some DICOM tag + - "OrthancPluginRestApiGet2()" to provide HTTP headers when calling Orthanc API + - "OrthancPluginGetInstanceOrigin()" to know through which mechanism an instance was received + - "OrthancPluginCreateImage()" and "OrthancPluginCreateImageAccessor()" to create images + - "OrthancPluginDecodeDicomImage()" to decode DICOM images + - "OrthancPluginComputeMd5()" and "OrthancPluginComputeSha1()" to compute MD5/SHA-1 hash +* New events in change callbacks: + - "OrthancStarted" + - "OrthancStopped" + - "UpdatedAttachment" + - "UpdatedMetadata" +* "/system" URI gives information about the plugins used for storage area and DB back-end +* Plugin callbacks must now return explicit "OrthancPluginErrorCode" (instead of integers) Lua --- @@ -25,15 +496,17 @@ Maintenance ----------- -* Full refactoring of the searching features -* C-Move SCP for studies using AccessionNumber tag -* Fix issue 4 (C-Store Association not renegotiated on Specific-to-specific transfer syntax change) -* "/system" URI gives information about the plugins used for storage area and DB back-end -* Plugin callbacks should now return explicit "OrthancPluginErrorCode" instead of integers -* "/tools/create-dicom" can create tags with unknown VR +* Full indexation of the patient/study tags to speed up searches and C-FIND +* Many refactorings, notably of the searching features and of the image decoding +* C-MOVE SCP for studies using AccessionNumber tag +* Fix issue 4 (C-STORE Association not renegotiated on Specific-to-specific transfer syntax change) +* Fix formatting of multipart HTTP answers * "--logdir" flag creates a single log file instead of 3 separate files for errors/warnings/infos * "--errors" flag lists the error codes that could be returned by Orthanc * Under Windows, the exit status of Orthanc corresponds to the encountered error code +* New "AgfaImpax", "EFilm2" and "Vitrea" modality manufacturers +* C-FIND SCP will return tags with sequence value representation +* Upgrade to Boost 1.59.0 for static builds Version 0.9.4 (2015/09/16) @@ -77,7 +550,7 @@ * If error while calling the REST API, the answer body contains description of the error (this feature can be disabled with the "HttpDescribeErrors" option) * Upgrade to curl 7.44.0 for static and Windows builds -* Upgrade to libcurl 1.0.2d for static and Windows builds +* Upgrade to openssl 1.0.2d for static and Windows builds * Depends on libjpeg 9a * Bypass zlib uncompression if "StorageCompression" is enabled and HTTP client supports deflate @@ -108,7 +581,7 @@ ------- * The configuration can be splitted into several files stored inside the same folder -* Custom setting of the local AET during C-Store SCU (both in Lua and in the REST API) +* Custom setting of the local AET during C-STORE SCU (both in Lua and in the REST API) * Many code refactorings Lua @@ -127,9 +600,9 @@ Fixes ----- -* Fix compatibility issues for C-Find SCU to Siemens Syngo.Via modalities SCP +* Fix compatibility issues for C-FIND SCU to Siemens Syngo.Via modalities SCP * Fix issue 15 (Lua scripts making HTTP requests) -* Fix issue 35 (Characters in PatientID string are not protected for C-Find) +* Fix issue 35 (Characters in PatientID string are not protected for C-FIND) * Fix issue 37 (Hyphens trigger range query even if datatype does not support ranges) @@ -140,7 +613,7 @@ ----- * DICOM Query/Retrieve available from Orthanc Explorer -* C-Move SCU and C-Find SCU are accessible through the REST API +* C-MOVE SCU and C-FIND SCU are accessible through the REST API * "?expand" flag for URIs "/patients", "/studies" and "/series" * "/tools/find" URI to search for DICOM resources from REST * Support of FreeBSD @@ -150,15 +623,15 @@ ----- * Speed-up in Orthanc Explorer for large amount of images -* Speed-up of the C-Find SCP server of Orthanc +* Speed-up of the C-FIND SCP server of Orthanc * Allow replacing PatientID/StudyInstanceUID/SeriesInstanceUID from Lua scripts -* Option "CaseSensitivePN" to enable case-insensitive C-Find SCP +* Option "CaseSensitivePN" to enable case-insensitive C-FIND SCP Fixes ----- * Prevent freeze on C-FIND if no DICOM tag is to be returned -* Fix slow C-Store SCP on recent versions of Linux, if +* Fix slow C-STORE SCP on recent versions of GNU/Linux, if USE_SYSTEM_DCMTK is set to OFF (http://forum.dcmtk.org/viewtopic.php?f=1&t=4009) * Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) * Fix issue 32 (Cyrillic symbols): Introduction of the "Windows1251" encoding @@ -229,7 +702,7 @@ * "/instances-tags" to get the tags of all the child instances of a patient/study/series with a single REST call (bulk tags retrieval) -* Configuration/Lua to select the accepted C-Store SCP transfer syntaxes +* Configuration/Lua to select the accepted C-STORE SCP transfer syntaxes * Fix reporting of errors in Orthanc Explorer when sending images to peers/modalities * Installation of plugin SDK in CMake @@ -340,7 +813,7 @@ Version 0.7.5 (2014/05/08) ========================== -* Dynamic negotiation of SOP classes for C-Store SCU +* Dynamic negotiation of SOP classes for C-STORE SCU * Creation of DICOM instances using the REST API * Embedding of images within DICOM instances * Adding/removal/modification of remote modalities/peers through REST @@ -379,16 +852,16 @@ * Possibility to disable the HTTP server or the DICOM server * Automatic computation of MD5 hashes for the stored DICOM files * Maintenance tool to recover DICOM files compressed by Orthanc -* The newline characters in the configuration file are fixed for Linux -* Capture of the SIGTERM signal in Linux +* The newline characters in the configuration file are fixed for GNU/Linux +* Capture of the SIGTERM signal in GNU/Linux Version 0.7.2 (2013/11/08) ========================== * Support of Query/Retrieve from medInria -* Accept more transfer syntaxes for C-Store SCP and SCU (notably JPEG) -* Create the meta-header when receiving files through C-Store SCP +* Accept more transfer syntaxes for C-STORE SCP and SCU (notably JPEG) +* Create the meta-header when receiving files through C-STORE SCP * Fixes and improvements thanks to the static analyzer cppcheck @@ -431,7 +904,7 @@ ========================== * Detection of stable patients/studies/series -* C-Find SCU at the instance level +* C-FIND SCU at the instance level * Link from modified to original resource in Orthanc Explorer * Fix of issue #8 * Anonymization of the medical alerts tag (0010,2000) @@ -541,7 +1014,7 @@ * The patient/study/series/instances are now indexed by SHA-1 digests of their DICOM Instance IDs (and not by UUIDs anymore): The same DICOM objects are thus always identified by the same Orthanc IDs -* Log of exported instances through DICOM C-Store SCU ("/exported" URI) +* Log of exported instances through DICOM C-STORE SCU ("/exported" URI) * Full refactoring of the DB schema and of the REST API * Introduction of generic classes for REST APIs (in Core/RestApi) @@ -572,7 +1045,7 @@ Version 0.2.2 (2012/10/04) ========================== -* Switch to Google Logging +* Switch to Google Log * Fixes to Debian packaging
--- a/OrthancExplorer/explorer.html Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancExplorer/explorer.html Fri Oct 12 15:18:10 2018 +0200 @@ -34,25 +34,139 @@ <script src="../plugins/explorer.js"></script> </head> <body> - <div data-role="page" id="find-patients" > + <div data-role="page" id="lookup" > <div data-role="header" > - <h1><span class="orthanc-name"></span>Find a patient</h1> - <a href="#plugins" data-icon="grid" class="ui-btn-left" data-direction="reverse">Plugins</a> + <h1><span class="orthanc-name"></span>Lookup studies</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> + <form data-ajax="false" id="lookup-form"> + <div data-role="fieldcontain"> + <label for="lookup-patient-id">Patient ID:</label> + <input type="text" name="lookup-patient-id" id="lookup-patient-id" value="" /> + </div> + + <div data-role="fieldcontain"> + <label for="lookup-patient-name">Patient Name:</label> + <input type="text" name="lookup-patient-name" id="lookup-patient-name" value="" /> + </div> + + <div data-role="fieldcontain"> + <label for="lookup-accession-number">Accession Number:</label> + <input type="text" name="lookup-accession-number" id="lookup-accession-number" value="" /> + </div> + + <div data-role="fieldcontain"> + <label for="lookup-study-description">Study Description:</label> + <input type="text" name="lookup-study-description" id="lookup-study-description" value="" /> + </div> + + <div data-role="fieldcontain"> + <label for="lookup-study-date">Study Date:</label> + <select name="lookup-study-date" id="lookup-study-date"> + </select> + </div> + + <fieldset class="ui-grid-b"> + <div class="ui-block-a"> + <a href="#find-patients" data-role="button" data-theme="b" data-direction="reverse">All patients</a> + </div> + <div class="ui-block-b"> + <a href="#find-studies" data-role="button" data-theme="b" data-direction="reverse">All studies</a> + </div> + <div class="ui-block-c"> + <button id="lookup-submit" type="submit" data-theme="e">Do lookup</button> + </div> + </fieldset> + <div> </div> + </form> + <div id="lookup-result"> + <div id="lookup-alert"> + <div class="ui-bar ui-bar-e"> + <h3>Warning:</h3> Your lookup led to many results! + Showing only <span id="lookup-count">?</span> studies to + avoid performance issue. Please make your query more + specific, then relaunch the lookup. + </div> + <div> </div> + </div> + <ul data-role="listview" data-inset="true" data-filter="true"> + </ul> + </div> + </div> + </div> + + <div data-role="page" id="find-patients" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>All patients</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#upload" data-icon="gear" data-role="button">Upload</a> + <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> + </div> + </div> + <div data-role="content"> + <div id="alert-patients"> + <div class="ui-bar ui-bar-e"> + <h3>Warning:</h3> This is a large Orthanc server. Showing + only <span id="count-patients">?</span> patients to avoid + performance issue. Make sure to use lookup if targeting + specific patients! + </div> + <div> </div> + </div> <ul id="all-patients" data-role="listview" data-inset="true" data-filter="true"> </ul> </div> </div> - <div data-role="page" id="upload" > + <div data-role="page" id="find-studies" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>All studies</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#upload" data-icon="gear" data-role="button">Upload</a> + <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> + </div> + </div> + <div data-role="content"> + <div id="alert-studies"> + <div class="ui-bar ui-bar-e"> + <h3>Warning:</h3> This is a large Orthanc server. Showing + only <span id="count-studies">?</span> studies to avoid + performance issue. Make sure to use lookup if targeting + specific studies! + </div> + <div> </div> + </div> + <ul id="all-studies" data-role="listview" data-inset="true" data-filter="true"> + </ul> + </div> + </div> + + <div data-role="page" id="upload"> <div data-role="header" > <h1><span class="orthanc-name"></span>Upload DICOM files</h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> </div> <div data-role="content"> <div style="display:none"> @@ -78,10 +192,14 @@ <div data-role="page" id="patient" > <div data-role="header" > <h1><span class="orthanc-name"></span>Patient</h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -135,10 +253,14 @@ <a href="#" class="patient-link">Patient</a> » Study </h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -186,11 +308,14 @@ <a href="#" class="study-link">Study</a> » Series </h1> - - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -240,10 +365,14 @@ <a href="#" class="series-link">Series</a> » Instance </h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> <a href="#upload" data-icon="gear" data-role="button">Upload</a> <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> </div> </div> <div data-role="content"> @@ -292,7 +421,10 @@ <div data-role="page" id="plugins" > <div data-role="header" > <h1><span class="orthanc-name"></span>Plugins</h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> </div> <div data-role="content"> <ul id="all-plugins" data-role="listview" data-inset="true" data-filter="true"> @@ -302,8 +434,11 @@ <div data-role="page" id="query-retrieve" > <div data-role="header" > - <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (1/3)</h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (1/4)</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> </div> <div data-role="content"> <form data-ajax="false"> @@ -349,6 +484,8 @@ <input type="checkbox" name="PT" id="qr-pt" class="custom" /> <label for="qr-pt">PT</label> <input type="checkbox" name="US" id="qr-us" class="custom" /> <label for="qr-us">US</label> <input type="checkbox" name="XA" id="qr-xa" class="custom" /> <label for="qr-xa">XA</label> + <input type="checkbox" name="DR" id="qr-dr" class="custom" /> <label for="qr-dr">DR</label> + <input type="checkbox" name="DX" id="qr-dx" class="custom" /> <label for="qr-dx">DX</label> </fieldset> </div> </div> @@ -368,8 +505,11 @@ <div data-role="page" id="query-retrieve-2" > <div data-role="header" > - <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (2/3)</h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (2/4)</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a> </div> <div data-role="content"> @@ -381,17 +521,91 @@ <div data-role="page" id="query-retrieve-3" > <div data-role="header" > - <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (3/3)</h1> - <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (3/4)</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a> </div> <div data-role="content"> - <ul data-role="listview" data-inset="true" data-filter="true" data-split-icon="arrow-d" data-split-theme="b"> + <ul data-role="listview" data-inset="true" data-filter="true"> </ul> </div> </div> + <div data-role="page" id="query-retrieve-4" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (4/4)</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> + <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a> + </div> + + <div data-role="content"> + <form data-ajax="false" id="retrieve-form"> + <div data-role="fieldcontain"> + <label for="retrieve-target">Target AET:</label> + <input type="text" name="retrieve-target" id="retrieve-target"></input> + </div> + + <fieldset class="ui-grid-b"> + <div class="ui-block-a"></div> + <div class="ui-block-b"> + <button id="retrieve-submit" type="submit" data-theme="b">Retrieve</button> + </div> + <div class="ui-block-c"></div> + </fieldset> + </form> + </div> + </div> + + + <div data-role="page" id="jobs" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>Jobs</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> + </div> + <div data-role="content"> + <ul id="all-jobs" data-role="listview" data-inset="true" data-filter="true"> + </ul> + </div> + </div> + + <div data-role="page" id="job" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>Job</h1> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-left"> + <a href="#lookup" data-icon="arrow-r" data-role="button" data-direction="reverse">Lookup</a> + <a href="#plugins" data-icon="grid" data-role="button" data-direction="reverse">Plugins</a> + </div> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#jobs" data-icon="refresh" data-role="button" data-direction="reverse">Jobs</a> + </div> + </div> + <div data-role="content"> + <ul data-role="listview" data-inset="true" data-filter="true" id="job-info"> + </ul> + + <fieldset class="ui-grid-b"> + <div class="ui-block-a"></div> + <div class="ui-block-b"> + <button id="job-cancel" data-theme="b">Cancel job</button> + <button id="job-resubmit" data-theme="b">Resubmit job</button> + <button id="job-pause" data-theme="b">Pause job</button> + <button id="job-resume" data-theme="b">Resume job</button> + </div> + <div class="ui-block-c"></div> + </fieldset> + </div> + </div> + <div id="peer-store" style="display:none;" class="ui-body-c"> <p align="center"><b>Sending to Orthanc peer...</b></p> <p><img src="libs/images/ajax-loader.gif" alt="" /></p>
--- a/OrthancExplorer/explorer.js Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancExplorer/explorer.js Fri Oct 12 15:18:10 2018 +0200 @@ -13,21 +13,40 @@ //$.mobile.defaultPageTransition = 'slide'; +var LIMIT_RESOURCES = 100; + var currentPage = ''; var currentUuid = ''; -// http://stackoverflow.com/a/4673436 -String.prototype.format = function() { - var args = arguments; - return this.replace(/{(\d+)}/g, function(match, number) { - /*return typeof args[number] != 'undefined' - ? args[number] - : match;*/ +function DeepCopy(obj) +{ + return jQuery.extend(true, {}, obj); +} + - return args[number]; - }); -}; +function ChangePage(page, options) +{ + var first = true; + if (options) { + for (var key in options) { + var value = options[key]; + if (first) { + page += '?'; + first = false; + } else { + page += '&'; + } + + page += key + '=' + value; + } + } + + window.location.replace('explorer.html#' + page); + /*$.mobile.changePage('#' + page, { + changeHash: true + });*/ +} function Refresh() @@ -179,29 +198,34 @@ } -function CompleteFormatting(s, link, isReverse) +function CompleteFormatting(node, link, isReverse, count) { + if (count != null) + { + node = node.add($('<span>') + .addClass('ui-li-count') + .text(count)); + } + if (link != null) { - s = 'href="' + link + '">' + s + '</a>'; - + node = $('<a>').attr('href', link).append(node); + if (isReverse) - s = 'data-direction="reverse" '+ s; - - s = '<a ' + s; + node.attr('data-direction', 'reverse') } + node = $('<li>').append(node); + if (isReverse) - return '<li data-icon="back">' + s + '</li>'; - else - return '<li>' + s + '</li>'; + node.attr('data-icon', 'back'); + + return node; } -function FormatMainDicomTags(tags, tagsToIgnore) +function FormatMainDicomTags(target, tags, tagsToIgnore) { - var s = ''; - for (var i in tags) { if (tagsToIgnore.indexOf(i) == -1) @@ -220,47 +244,52 @@ v = SplitLongUid(v); } - - s += ('<p>{0}: <strong>{1}</strong></p>').format(i, v); + target.append($('<p>') + .text(i + ': ') + .append($('<strong>').text(v))); } } - - return s; } function FormatPatient(patient, link, isReverse) { - var s = ('<h3>{0}</h3>{1}' + - '<span class="ui-li-count">{2}</span>' - ).format - (patient.MainDicomTags.PatientName, - FormatMainDicomTags(patient.MainDicomTags, [ - "PatientName" - /*"OtherPatientIDs" */ - ]), - patient.Studies.length - ); + var node = $('<div>').append($('<h3>').text(patient.MainDicomTags.PatientName)); - return CompleteFormatting(s, link, isReverse); + FormatMainDicomTags(node, patient.MainDicomTags, [ + "PatientName" + // "OtherPatientIDs" + ]); + + return CompleteFormatting(node, link, isReverse, patient.Studies.length); } -function FormatStudy(study, link, isReverse) +function FormatStudy(study, link, isReverse, includePatient) { - var s = ('<h3>{0}</h3>{1}' + - '<span class="ui-li-count">{2}</span>' - ).format - (study.MainDicomTags.StudyDescription, - FormatMainDicomTags(study.MainDicomTags, [ - "StudyDescription", - "StudyTime" - ]), - study.Series.length - ); + var label; + + if (includePatient) { + label = study.Label; + } else { + label = study.MainDicomTags.StudyDescription; + } + + var node = $('<div>').append($('<h3>').text(label)); - return CompleteFormatting(s, link, isReverse); + if (includePatient) { + FormatMainDicomTags(node, study.PatientMainDicomTags, [ + 'PatientName' + ]); + } + + FormatMainDicomTags(node, study.MainDicomTags, [ + 'StudyDescription', + 'StudyTime' + ]); + + return CompleteFormatting(node, link, isReverse, study.Series.length); } @@ -277,41 +306,39 @@ { c = series.Instances.length + '/' + series.ExpectedNumberOfInstances; } + + var node = $('<div>') + .append($('<h3>').text(series.MainDicomTags.SeriesDescription)) + .append($('<p>').append($('<em>') + .text('Status: ') + .append($('<strong>').text(series.Status)))); - var s = ('<h3>{0}</h3>' + - '<p><em>Status: <strong>{1}</strong></em></p>{2}' + - '<span class="ui-li-count">{3}</span>').format - (series.MainDicomTags.SeriesDescription, - series.Status, - FormatMainDicomTags(series.MainDicomTags, [ + FormatMainDicomTags(node, series.MainDicomTags, [ "SeriesDescription", "SeriesTime", "Manufacturer", "ImagesInAcquisition", "SeriesDate", "ImageOrientationPatient" - ]), - c - ); - - return CompleteFormatting(s, link, isReverse); + ]); + + return CompleteFormatting(node, link, isReverse, c); } function FormatInstance(instance, link, isReverse) { - var s = ('<h3>Instance {0}</h3>{1}').format - (instance.IndexInSeries, - FormatMainDicomTags(instance.MainDicomTags, [ - "AcquisitionNumber", - "InstanceNumber", - "InstanceCreationDate", - "InstanceCreationTime", - "ImagePositionPatient" - ]) - ); + var node = $('<div>').append($('<h3>').text('Instance: ' + instance.IndexInSeries)); - return CompleteFormatting(s, link, isReverse); + FormatMainDicomTags(node, instance.MainDicomTags, [ + "AcquisitionNumber", + "InstanceNumber", + "InstanceCreationDate", + "InstanceCreationTime", + "ImagePositionPatient" + ]); + + return CompleteFormatting(node, link, isReverse); } @@ -323,7 +350,11 @@ cache: false, success: function(s) { if (s.Name != "") { - $('.orthanc-name').html('<a class="ui-link" href="explorer.html">' + s.Name + '</a> » '); + $('.orthanc-name').html($('<a>') + .addClass('ui-link') + .attr('href', 'explorer.html') + .text(s.Name) + .append(' » ')); } } }); @@ -331,19 +362,166 @@ +$('#lookup').live('pagebeforeshow', function() { + // NB: "GenerateDicomDate()" is defined in "query-retrieve.js" + var target = $('#lookup-study-date'); + $('option', target).remove(); + target.append($('<option>').attr('value', '*').text('Any date')); + target.append($('<option>').attr('value', GenerateDicomDate(0)).text('Today')); + target.append($('<option>').attr('value', GenerateDicomDate(-1)).text('Yesterday')); + target.append($('<option>').attr('value', GenerateDicomDate(-7) + '-').text('Last 7 days')); + target.append($('<option>').attr('value', GenerateDicomDate(-31) + '-').text('Last 31 days')); + target.append($('<option>').attr('value', GenerateDicomDate(-31 * 3) + '-').text('Last 3 months')); + target.append($('<option>').attr('value', GenerateDicomDate(-365) + '-').text('Last year')); + target.selectmenu('refresh'); + + $('#lookup-result').hide(); +}); + + +$('#lookup-submit').live('click', function() { + $('#lookup-result').hide(); + + var lookup = { + 'Level' : 'Study', + 'Expand' : true, + 'Limit' : LIMIT_RESOURCES + 1, + 'Query' : { + 'StudyDate' : $('#lookup-study-date').val() + } + }; + + $('#lookup-form input').each(function(index, input) { + if (input.value.length != 0) { + if (input.id == 'lookup-patient-id') { + lookup['Query']['PatientID'] = input.value; + } + else if (input.id == 'lookup-patient-name') { + lookup['Query']['PatientName'] = input.value; + } + else if (input.id == 'lookup-accession-number') { + lookup['Query']['AccessionNumber'] = input.value; + } + else if (input.id == 'lookup-study-description') { + lookup['Query']['StudyDescription'] = input.value; + } + else { + console.error('Unknown lookup field: ' + input.id); + } + } + }); + + $.ajax({ + url: '../tools/find', + type: 'POST', + data: JSON.stringify(lookup), + dataType: 'json', + async: false, + error: function() { + alert('Error during lookup'); + }, + success: function(studies) { + FormatListOfStudies('#lookup-result ul', '#lookup-alert', '#lookup-count', studies); + $('#lookup-result').show(); + } + }); + + return false; +}); + + $('#find-patients').live('pagebeforeshow', function() { - GetResource('/patients?expand', function(patients) { - var target = $('#all-patients'); - $('li', target).remove(); + GetResource('/patients?expand&since=0&limit=' + (LIMIT_RESOURCES + 1), function(patients) { + var target = $('#all-patients'); + $('li', target).remove(); - SortOnDicomTag(patients, 'PatientName', false, false); + SortOnDicomTag(patients, 'PatientName', false, false); + + var count, showAlert; + if (patients.length <= LIMIT_RESOURCES) { + count = patients.length; + showAlert = false; + } + else { + count = LIMIT_RESOURCES; + showAlert = true; + } + + for (var i = 0; i < count; i++) { + var p = FormatPatient(patients[i], '#patient?uuid=' + patients[i].ID); + target.append(p); + } + + target.listview('refresh'); - for (var i = 0; i < patients.length; i++) { - var p = FormatPatient(patients[i], '#patient?uuid=' + patients[i].ID); - target.append(p); + if (showAlert) { + $('#count-patients').text(LIMIT_RESOURCES); + $('#alert-patients').show(); + } else { + $('#alert-patients').hide(); + } + }); +}); + + + +function FormatListOfStudies(targetId, alertId, countId, studies) +{ + var target = $(targetId); + $('li', target).remove(); + + for (var i = 0; i < studies.length; i++) { + var patient = studies[i].PatientMainDicomTags.PatientName; + var study = studies[i].MainDicomTags.StudyDescription; + + var s; + if (typeof patient === 'string') { + s = patient; + } + + if (typeof study === 'string') { + if (s.length > 0) { + s += ' - '; } - target.listview('refresh'); + s += study; + } + + studies[i]['Label'] = s; + } + + Sort(studies, function(a) { return a.Label }, false, false); + + + var count, showAlert; + if (studies.length <= LIMIT_RESOURCES) { + count = studies.length; + showAlert = false; + } + else { + count = LIMIT_RESOURCES; + showAlert = true; + } + + for (var i = 0; i < count; i++) { + var p = FormatStudy(studies[i], '#study?uuid=' + studies[i].ID, false, true); + target.append(p); + } + + target.listview('refresh'); + + if (showAlert) { + $(countId).text(LIMIT_RESOURCES); + $(alertId).show(); + } else { + $(alertId).hide(); + } +} + + +$('#find-studies').live('pagebeforeshow', function() { + GetResource('/studies?expand&since=0&limit=' + (LIMIT_RESOURCES + 1), function(studies) { + FormatListOfStudies('#all-studies', '#alert-studies', '#count-studies', studies); }); }); @@ -369,8 +547,10 @@ function RefreshPatient() { if ($.mobile.pageData) { - GetResource('/patients/' + $.mobile.pageData.uuid, function(patient) { - GetResource('/patients/' + $.mobile.pageData.uuid + '/studies', function(studies) { + var pageData = DeepCopy($.mobile.pageData); + + GetResource('/patients/' + pageData.uuid, function(patient) { + GetResource('/patients/' + pageData.uuid + '/studies', function(studies) { SortOnDicomTag(studies, 'StudyDate', false, true); $('#patient-info li').remove(); @@ -385,8 +565,9 @@ for (var i = 0; i < studies.length; i++) { if (i == 0 || studies[i].MainDicomTags.StudyDate != studies[i - 1].MainDicomTags.StudyDate) { - target.append('<li data-role="list-divider">{0}</li>'.format - (FormatDicomDate(studies[i].MainDicomTags.StudyDate))); + target.append($('<li>') + .attr('data-role', 'list-divider') + .text(FormatDicomDate(studies[i].MainDicomTags.StudyDate))); } target.append(FormatStudy(studies[i], '#study?uuid=' + studies[i].ID)); @@ -399,7 +580,7 @@ // Check whether this patient is protected $.ajax({ - url: '../patients/' + $.mobile.pageData.uuid + '/protected', + url: '../patients/' + pageData.uuid + '/protected', type: 'GET', dataType: 'text', async: false, @@ -411,7 +592,7 @@ }); currentPage = 'patient'; - currentUuid = $.mobile.pageData.uuid; + currentUuid = pageData.uuid; }); }); } @@ -421,9 +602,11 @@ function RefreshStudy() { if ($.mobile.pageData) { - GetResource('/studies/' + $.mobile.pageData.uuid, function(study) { + var pageData = DeepCopy($.mobile.pageData); + + GetResource('/studies/' + pageData.uuid, function(study) { GetResource('/patients/' + study.ParentPatient, function(patient) { - GetResource('/studies/' + $.mobile.pageData.uuid + '/series', function(series) { + GetResource('/studies/' + pageData.uuid + '/series', function(series) { SortOnDicomTag(series, 'SeriesDate', false, true); $('#study .patient-link').attr('href', '#patient?uuid=' + patient.ID); @@ -443,15 +626,17 @@ for (var i = 0; i < series.length; i++) { if (i == 0 || series[i].MainDicomTags.SeriesDate != series[i - 1].MainDicomTags.SeriesDate) { - target.append('<li data-role="list-divider">{0}</li>'.format - (FormatDicomDate(series[i].MainDicomTags.SeriesDate))); + target.append($('<li>') + .attr('data-role', 'list-divider') + .text(FormatDicomDate(series[i].MainDicomTags.SeriesDate))); } + target.append(FormatSeries(series[i], '#series?uuid=' + series[i].ID)); } target.listview('refresh'); currentPage = 'study'; - currentUuid = $.mobile.pageData.uuid; + currentUuid = pageData.uuid; }); }); }); @@ -462,10 +647,12 @@ function RefreshSeries() { if ($.mobile.pageData) { - GetResource('/series/' + $.mobile.pageData.uuid, function(series) { + var pageData = DeepCopy($.mobile.pageData); + + GetResource('/series/' + pageData.uuid, function(series) { GetResource('/studies/' + series.ParentStudy, function(study) { GetResource('/patients/' + study.ParentPatient, function(patient) { - GetResource('/series/' + $.mobile.pageData.uuid + '/instances', function(instances) { + GetResource('/series/' + pageData.uuid + '/instances', function(instances) { Sort(instances, function(x) { return x.IndexInSeries; }, true, false); $('#series .patient-link').attr('href', '#patient?uuid=' + patient.ID); @@ -492,7 +679,7 @@ target.listview('refresh'); currentPage = 'series'; - currentUuid = $.mobile.pageData.uuid; + currentUuid = pageData.uuid; }); }); }); @@ -501,6 +688,24 @@ } +function EscapeHtml(value) +{ + var ENTITY_MAP = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + return String(value).replace(/[&<>"'`=\/]/g, function (s) { + return ENTITY_MAP[s]; + }); +} + function ConvertForTree(dicom) { @@ -508,12 +713,14 @@ for (var i in dicom) { if (dicom[i] != null) { - var label = i + '<span class="tag-name"> (<i>' + dicom[i]["Name"] + '</i>)</span>: '; + var label = (i + '<span class="tag-name"> (<i>' + + EscapeHtml(dicom[i]["Name"]) + + '</i>)</span>: '); if (dicom[i]["Type"] == 'String') { result.push({ - label: label + '<strong>' + dicom[i]["Value"] + '</strong>', + label: label + '<strong>' + EscapeHtml(dicom[i]["Value"]) + '</strong>', children: [] }); } @@ -556,7 +763,9 @@ function RefreshInstance() { if ($.mobile.pageData) { - GetResource('/instances/' + $.mobile.pageData.uuid, function(instance) { + var pageData = DeepCopy($.mobile.pageData); + + GetResource('/instances/' + pageData.uuid, function(instance) { GetResource('/series/' + instance.ParentSeries, function(series) { GetResource('/studies/' + series.ParentStudy, function(study) { GetResource('/patients/' + study.ParentPatient, function(patient) { @@ -585,7 +794,7 @@ SetupAnonymizedOrModifiedFrom('#instance-modified-from', instance, 'instance', 'ModifiedFrom'); currentPage = 'instance'; - currentUuid = $.mobile.pageData.uuid; + currentUuid = pageData.uuid; }); }); }); @@ -704,7 +913,9 @@ $('#instance-preview').live('click', function(e) { if ($.mobile.pageData) { - var pdf = '../instances/' + $.mobile.pageData.uuid + '/pdf'; + var pageData = DeepCopy($.mobile.pageData); + + var pdf = '../instances/' + pageData.uuid + '/pdf'; $.ajax({ url: pdf, cache: false, @@ -712,11 +923,11 @@ window.location.assign(pdf); }, error: function() { - GetResource('/instances/' + $.mobile.pageData.uuid + '/frames', function(frames) { + GetResource('/instances/' + pageData.uuid + '/frames', function(frames) { if (frames.length == 1) { // Viewing a single-frame image - jQuery.slimbox('../instances/' + $.mobile.pageData.uuid + '/preview', '', { + jQuery.slimbox('../instances/' + pageData.uuid + '/preview', '', { overlayFadeDuration : 1, resizeDuration : 1, imageFadeDuration : 1 @@ -728,7 +939,7 @@ var images = []; for (var i = 0; i < frames.length; i++) { - images.push([ '../instances/' + $.mobile.pageData.uuid + '/frames/' + i + '/preview' ]); + images.push([ '../instances/' + pageData.uuid + '/frames/' + i + '/preview' ]); } jQuery.slimbox(images, 0, { @@ -748,14 +959,16 @@ $('#series-preview').live('click', function(e) { if ($.mobile.pageData) { - GetResource('/series/' + $.mobile.pageData.uuid, function(series) { - GetResource('/series/' + $.mobile.pageData.uuid + '/instances', function(instances) { + var pageData = DeepCopy($.mobile.pageData); + + GetResource('/series/' + pageData.uuid, function(series) { + GetResource('/series/' + pageData.uuid + '/instances', function(instances) { Sort(instances, function(x) { return x.IndexInSeries; }, true, false); var images = []; for (var i = 0; i < instances.length; i++) { images.push([ '../instances/' + instances[i].ID + '/preview', - '{0}/{1}'.format(i + 1, instances.length) ]) + (i + 1).toString() + '/' + instances.length.toString() ]) } jQuery.slimbox(images, 0, { @@ -858,6 +1071,8 @@ $('#instance-store,#series-store,#study-store,#patient-store').live('click', function(e) { ChooseDicomModality(function(modality, peer) { + var pageData = DeepCopy($.mobile.pageData); + var url; var loading; @@ -878,7 +1093,7 @@ url: url, type: 'POST', dataType: 'text', - data: $.mobile.pageData.uuid, + data: pageData.uuid, async: true, // Necessary to block UI beforeSend: function() { $.blockUI({ message: $(loading) }); @@ -1052,3 +1267,209 @@ } }); }); + + + +function ParseJobTime(s) +{ + var t = (s.substr(0, 4) + '-' + + s.substr(4, 2) + '-' + + s.substr(6, 5) + ':' + + s.substr(11, 2) + ':' + + s.substr(13)); + var utc = new Date(t); + + // Convert from UTC to local time + return new Date(utc.getTime() - utc.getTimezoneOffset() * 60000); +} + + +function AddJobField(target, description, field) +{ + if (!(typeof field === 'undefined')) { + target.append($('<p>') + .text(description) + .append($('<strong>').text(field))); + } +} + + +function AddJobDateField(target, description, field) +{ + if (!(typeof field === 'undefined')) { + target.append($('<p>') + .text(description) + .append($('<strong>').text(ParseJobTime(field)))); + } +} + + +$('#jobs').live('pagebeforeshow', function() { + $.ajax({ + url: '../jobs?expand', + dataType: 'json', + async: false, + cache: false, + success: function(jobs) { + var target = $('#all-jobs'); + $('li', target).remove(); + + var running = $('<li>') + .attr('data-role', 'list-divider') + .text('Currently running'); + + var pending = $('<li>') + .attr('data-role', 'list-divider') + .text('Pending jobs'); + + var inactive = $('<li>') + .attr('data-role', 'list-divider') + .text('Inactive jobs'); + + target.append(running); + target.append(pending); + target.append(inactive); + + jobs.map(function(job) { + var li = $('<li>'); + var item = $('<a>'); + li.append(item); + item.attr('href', '#job?uuid=' + job.ID); + item.append($('<h1>').text(job.Type)); + item.append($('<span>').addClass('ui-li-count').text(job.State)); + AddJobField(item, 'ID: ', job.ID); + AddJobField(item, 'Local AET: ', job.Content.LocalAet); + AddJobField(item, 'Remote AET: ', job.Content.RemoteAet); + AddJobDateField(item, 'Creation time: ', job.CreationTime); + AddJobDateField(item, 'Completion time: ', job.CompletionTime); + AddJobDateField(item, 'ETA: ', job.EstimatedTimeOfArrival); + + if (job.State == 'Running' || + job.State == 'Pending' || + job.State == 'Paused') { + AddJobField(item, 'Priority: ', job.Priority); + AddJobField(item, 'Progress: ', job.Progress); + } + + if (job.State == 'Running') { + li.insertAfter(running); + } else if (job.State == 'Pending' || + job.State == 'Paused') { + li.insertAfter(pending); + } else { + li.insertAfter(inactive); + } + }); + + target.listview('refresh'); + } + }); +}); + + +$('#job').live('pagebeforeshow', function() { + if ($.mobile.pageData) { + var pageData = DeepCopy($.mobile.pageData); + + $.ajax({ + url: '../jobs/' + pageData.uuid, + dataType: 'json', + async: false, + cache: false, + success: function(job) { + var target = $('#job-info'); + $('li', target).remove(); + + target.append($('<li>') + .attr('data-role', 'list-divider') + .text('General information about the job')); + + var block = $('<li>'); + for (var i in job) { + if (i == 'CreationTime' || + i == 'CompletionTime' || + i == 'EstimatedTimeOfArrival') { + AddJobDateField(block, i + ': ', job[i]); + } else if (i != 'InternalContent' && + i != 'Content' && + i != 'Timestamp') { + AddJobField(block, i + ': ', job[i]); + } + } + + target.append(block); + + target.append($('<li>') + .attr('data-role', 'list-divider') + .text('Detailed information')); + + var block = $('<li>'); + + for (var item in job.Content) { + var value = job.Content[item]; + if (typeof value !== 'string') { + value = JSON.stringify(value); + } + + AddJobField(block, item + ': ', value); + } + + target.append(block); + + target.listview('refresh'); + + $('#job-cancel').closest('.ui-btn').hide(); + $('#job-retry').closest('.ui-btn').hide(); + $('#job-resubmit').closest('.ui-btn').hide(); + $('#job-pause').closest('.ui-btn').hide(); + $('#job-resume').closest('.ui-btn').hide(); + + if (job.State == 'Running' || + job.State == 'Pending' || + job.State == 'Retry') { + $('#job-cancel').closest('.ui-btn').show(); + $('#job-pause').closest('.ui-btn').show(); + } + else if (job.State == 'Success') { + } + else if (job.State == 'Failure') { + $('#job-resubmit').closest('.ui-btn').show(); + } + else if (job.State == 'Paused') { + $('#job-resume').closest('.ui-btn').show(); + } + } + }); + } +}); + + + +function TriggerJobAction(action) +{ + $.ajax({ + url: '../jobs/' + $.mobile.pageData.uuid + '/' + action, + type: 'POST', + async: false, + cache: false, + complete: function(s) { + window.location.reload(); + } + }); +} + +$('#job-cancel').live('click', function() { + TriggerJobAction('cancel'); +}); + +$('#job-resubmit').live('click', function() { + TriggerJobAction('resubmit'); +}); + +$('#job-pause').live('click', function() { + TriggerJobAction('pause'); +}); + +$('#job-resume').live('click', function() { + TriggerJobAction('resume'); +});
--- a/OrthancExplorer/file-upload.js Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancExplorer/file-upload.js Fri Oct 12 15:18:10 2018 +0200 @@ -48,6 +48,11 @@ $('#upload').live('pageshow', function() { + alert('WARNING - This page is currently affected by Orthanc issue #21: ' + + '"DICOM files might be missing after uploading with Mozilla Firefox." ' + + 'Do not use this upload feature for clinical uses, or carefully ' + + 'check that all instances have been properly received by Orthanc. ' + + 'Please use the command-line "ImportDicomFiles.py" script to circumvent this issue.'); $('#fileupload').fileupload('enable'); });
--- a/OrthancExplorer/query-retrieve.js Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancExplorer/query-retrieve.js Fri Oct 12 15:18:10 2018 +0200 @@ -1,294 +1,322 @@ -function JavascriptDateToDicom(date) -{ - var s = date.toISOString(); - return s.substring(0, 4) + s.substring(5, 7) + s.substring(8, 10); -} - -function GenerateDicomDate(days) -{ - var today = new Date(); - var other = new Date(today); - other.setDate(today.getDate() + days); - return JavascriptDateToDicom(other); -} - - -$('#query-retrieve').live('pagebeforeshow', function() { - $.ajax({ - url: '../modalities', - dataType: 'json', - async: false, - cache: false, - success: function(modalities) { - var target = $('#qr-server'); - $('option', target).remove(); - - for (var i = 0; i < modalities.length; i++) { - var option = $('<option>').text(modalities[i]); - target.append(option); - } - - target.selectmenu('refresh'); - } - }); - - var target = $('#qr-date'); - $('option', target).remove(); - target.append($('<option>').attr('value', '*').text('Any date')); - target.append($('<option>').attr('value', GenerateDicomDate(0)).text('Today')); - target.append($('<option>').attr('value', GenerateDicomDate(-1)).text('Yesterday')); - target.append($('<option>').attr('value', GenerateDicomDate(-7) + '-').text('Last 7 days')); - target.append($('<option>').attr('value', GenerateDicomDate(-31) + '-').text('Last 31 days')); - target.append($('<option>').attr('value', GenerateDicomDate(-31 * 3) + '-').text('Last 3 months')); - target.append($('<option>').attr('value', GenerateDicomDate(-365) + '-').text('Last year')); - target.selectmenu('refresh'); -}); - - -$('#qr-echo').live('click', function() { - var server = $('#qr-server').val(); - var message = 'Error: The C-Echo has failed!'; - - $.ajax({ - url: '../modalities/' + server + '/echo', - type: 'POST', - cache: false, - async: false, - success: function() { - message = 'The C-Echo has succeeded!'; - } - }); - - $('<div>').simpledialog2({ - mode: 'button', - headerText: 'Echo result', - headerClose: true, - buttonPrompt: message, - animate: false, - buttons : { - 'OK': { click: function () { } } - } - }); - - return false; -}); - - -$('#qr-submit').live('click', function() { - var query = { - 'Level' : 'Study', - 'Query' : { - 'AccessionNumber' : '*', - 'PatientBirthDate' : '*', - 'PatientID' : '*', - 'PatientName' : '*', - 'PatientSex' : '*', - 'SpecificCharacterSet' : 'ISO_IR 192', // UTF-8 - 'StudyDate' : $('#qr-date').val(), - 'StudyDescription' : '*' - } - }; - - var field = $('#qr-fields input:checked').val(); - query['Query'][field] = $('#qr-value').val().toUpperCase(); - - var modalities = ''; - $('#qr-modalities input:checked').each(function() { - var s = $(this).attr('name'); - if (modalities == '*') - modalities = s; - else - modalities += '\\' + s; - }); - - if (modalities.length > 0) { - query['Query']['ModalitiesInStudy'] = modalities; - } - - - var server = $('#qr-server').val(); - $.ajax({ - url: '../modalities/' + server + '/query', - type: 'POST', - data: JSON.stringify(query), - dataType: 'json', - async: false, - error: function() { - alert('Error during query (C-Find)'); - }, - success: function(result) { - window.location.assign('explorer.html#query-retrieve-2?server=' + server + '&uuid=' + result['ID']); - } - }); - - return false; -}); - - - -function Retrieve(url) -{ - $.ajax({ - url: '../system', - dataType: 'json', - async: false, - success: function(system) { - $('<div>').simpledialog2({ - mode: 'button', - headerText: 'Target', - headerClose: true, - buttonPrompt: 'Enter Application Entity Title (AET):', - buttonInput: true, - buttonInputDefault: system['DicomAet'], - buttons : { - 'OK': { - click: function () { - var aet = $.mobile.sdLastInput; - if (aet.length == 0) - aet = system['DicomAet']; - - $.ajax({ - url: url, - type: 'POST', - async: true, // Necessary to block UI - dataType: 'text', - data: aet, - beforeSend: function() { - $.blockUI({ message: $('#info-retrieve') }); - }, - complete: function(s) { - $.unblockUI(); - }, - error: function() { - alert('Error during retrieve'); - } - }); - } - } - } - }); - } - }); -} - - - - -$('#query-retrieve-2').live('pagebeforeshow', function() { - if ($.mobile.pageData) { - var uri = '../queries/' + $.mobile.pageData.uuid + '/answers'; - - $.ajax({ - url: uri, - dataType: 'json', - async: false, - success: function(answers) { - var target = $('#query-retrieve-2 ul'); - $('li', target).remove(); - - for (var i = 0; i < answers.length; i++) { - $.ajax({ - url: uri + '/' + answers[i] + '/content?simplify', - dataType: 'json', - async: false, - success: function(study) { - var series = '#query-retrieve-3?server=' + $.mobile.pageData.server + '&uuid=' + study['StudyInstanceUID']; - var info = $('<a>').attr('href', series).html( - ('<h3>{0} - {1}</h3>' + - '<p>Accession number: <b>{2}</b></p>' + - '<p>Birth date: <b>{3}</b></p>' + - '<p>Patient sex: <b>{4}</b></p>' + - '<p>Study description: <b>{5}</b></p>' + - '<p>Study date: <b>{6}</b></p>').format( - study['PatientID'], - study['PatientName'], - study['AccessionNumber'], - FormatDicomDate(study['PatientBirthDate']), - study['PatientSex'], - study['StudyDescription'], - FormatDicomDate(study['StudyDate']))); - - var studyUri = uri + '/' + answers[i] + '/retrieve'; - var retrieve = $('<a>').text('Retrieve').click(function() { - Retrieve(studyUri); - }); - - target.append($('<li>').append(info).append(retrieve)); - } - }); - } - - target.listview('refresh'); - } - }); - } -}); - - -$('#query-retrieve-3').live('pagebeforeshow', function() { - if ($.mobile.pageData) { - var query = { - 'Level' : 'Series', - 'Query' : { - 'Modality' : '*', - 'ProtocolName' : '*', - 'SeriesDescription' : '*', - 'SeriesInstanceUID' : '*', - 'StudyInstanceUID' : $.mobile.pageData.uuid - } - }; - - $.ajax({ - url: '../modalities/' + $.mobile.pageData.server + '/query', - type: 'POST', - data: JSON.stringify(query), - dataType: 'json', - async: false, - error: function() { - alert('Error during query (C-Find)'); - }, - success: function(answer) { - var uri = '../queries/' + answer['ID'] + '/answers'; - - $.ajax({ - url: uri, - dataType: 'json', - async: false, - success: function(answers) { - - var target = $('#query-retrieve-3 ul'); - $('li', target).remove(); - - for (var i = 0; i < answers.length; i++) { - $.ajax({ - url: uri + '/' + answers[i] + '/content?simplify', - dataType: 'json', - async: false, - success: function(series) { - var info = $('<a>').html( - ('<h3>{0}</h3>' + - '<p>Modality: <b>{1}</b></p>' + - '<p>Protocol name: <b>{2}</b></p>' - ).format( - series['SeriesDescription'], - series['Modality'], - series['ProtocolName'] - )); - - var seriesUri = uri + '/' + answers[i] + '/retrieve'; - var retrieve = $('<a>').text('Retrieve').click(function() { - Retrieve(seriesUri); - }); - - target.append($('<li>').append(info).append(retrieve)); - } - }); - } - - target.listview('refresh'); - } - }); - } - }); - } -}); +function JavascriptDateToDicom(date) +{ + var s = date.toISOString(); + return s.substring(0, 4) + s.substring(5, 7) + s.substring(8, 10); +} + +function GenerateDicomDate(days) +{ + var today = new Date(); + var other = new Date(today); + other.setDate(today.getDate() + days); + return JavascriptDateToDicom(other); +} + + +$('#query-retrieve').live('pagebeforeshow', function() { + $.ajax({ + url: '../modalities', + dataType: 'json', + async: false, + cache: false, + success: function(modalities) { + var target = $('#qr-server'); + $('option', target).remove(); + + for (var i = 0; i < modalities.length; i++) { + var option = $('<option>').text(modalities[i]); + target.append(option); + } + + target.selectmenu('refresh'); + } + }); + + var target = $('#qr-date'); + $('option', target).remove(); + target.append($('<option>').attr('value', '*').text('Any date')); + target.append($('<option>').attr('value', GenerateDicomDate(0)).text('Today')); + target.append($('<option>').attr('value', GenerateDicomDate(-1)).text('Yesterday')); + target.append($('<option>').attr('value', GenerateDicomDate(-7) + '-').text('Last 7 days')); + target.append($('<option>').attr('value', GenerateDicomDate(-31) + '-').text('Last 31 days')); + target.append($('<option>').attr('value', GenerateDicomDate(-31 * 3) + '-').text('Last 3 months')); + target.append($('<option>').attr('value', GenerateDicomDate(-365) + '-').text('Last year')); + target.selectmenu('refresh'); +}); + + +$('#qr-echo').live('click', function() { + var server = $('#qr-server').val(); + var message = 'Error: The C-Echo has failed!'; + + $.ajax({ + url: '../modalities/' + server + '/echo', + type: 'POST', + cache: false, + async: false, + success: function() { + message = 'The C-Echo has succeeded!'; + } + }); + + $('<div>').simpledialog2({ + mode: 'button', + headerText: 'Echo result', + headerClose: true, + buttonPrompt: message, + animate: false, + buttons : { + 'OK': { click: function () { } } + } + }); + + return false; +}); + + +$('#qr-submit').live('click', function() { + var query = { + 'Level' : 'Study', + 'Query' : { + 'AccessionNumber' : '*', + 'PatientBirthDate' : '*', + 'PatientID' : '*', + 'PatientName' : '*', + 'PatientSex' : '*', + 'StudyDate' : $('#qr-date').val(), + 'StudyDescription' : '*' + } + }; + + var field = $('#qr-fields input:checked').val(); + query['Query'][field] = $('#qr-value').val().toUpperCase(); + + var modalities = ''; + $('#qr-modalities input:checked').each(function() { + var s = $(this).attr('name'); + if (modalities == '') + modalities = s; + else + modalities += '\\' + s; + }); + + if (modalities.length > 0) { + query['Query']['ModalitiesInStudy'] = modalities; + } + + + var server = $('#qr-server').val(); + $.ajax({ + url: '../modalities/' + server + '/query', + type: 'POST', + data: JSON.stringify(query), + dataType: 'json', + async: false, + error: function() { + alert('Error during query (C-Find)'); + }, + success: function(result) { + ChangePage('query-retrieve-2', { + 'server' : server, + 'uuid' : result['ID'] + }); + } + }); + + return false; +}); + + + +$('#query-retrieve-2').live('pagebeforeshow', function() { + if ($.mobile.pageData) { + var pageData = DeepCopy($.mobile.pageData); + + var uri = '../queries/' + pageData.uuid + '/answers'; + + $.ajax({ + url: uri, + dataType: 'json', + async: false, + success: function(answers) { + var target = $('#query-retrieve-2 ul'); + $('li', target).remove(); + + for (var i = 0; i < answers.length; i++) { + $.ajax({ + url: uri + '/' + answers[i] + '/content?simplify', + dataType: 'json', + async: false, + success: function(study) { + var series = '#query-retrieve-3?server=' + pageData.server + '&uuid=' + study['StudyInstanceUID']; + + var content = ($('<div>') + .append($('<h3>').text(study['PatientID'] + ' - ' + study['PatientName'])) + .append($('<p>').text('Accession number: ') + .append($('<b>').text(study['AccessionNumber']))) + .append($('<p>').text('Birth date: ') + .append($('<b>').text(study['PatientBirthDate']))) + .append($('<p>').text('Patient sex: ') + .append($('<b>').text(study['PatientSex']))) + .append($('<p>').text('Study description: ') + .append($('<b>').text(study['StudyDescription']))) + .append($('<p>').text('Study date: ') + .append($('<b>').text(FormatDicomDate(study['StudyDate']))))); + + var info = $('<a>').attr('href', series).html(content); + + var answerId = answers[i]; + var retrieve = $('<a>').text('Retrieve all study').click(function() { + ChangePage('query-retrieve-4', { + 'query' : pageData.uuid, + 'answer' : answerId, + 'server' : pageData.server + }); + }); + + target.append($('<li>').append(info).append(retrieve)); + } + }); + } + + target.listview('refresh'); + } + }); + } +}); + + +$('#query-retrieve-3').live('pagebeforeshow', function() { + if ($.mobile.pageData) { + var pageData = DeepCopy($.mobile.pageData); + + var query = { + 'Level' : 'Series', + 'Query' : { + 'Modality' : '*', + 'ProtocolName' : '*', + 'SeriesDescription' : '*', + 'SeriesInstanceUID' : '*', + 'StudyInstanceUID' : pageData.uuid + } + }; + + $.ajax({ + url: '../modalities/' + pageData.server + '/query', + type: 'POST', + data: JSON.stringify(query), + dataType: 'json', + async: false, + error: function() { + alert('Error during query (C-Find)'); + }, + success: function(answer) { + var queryUuid = answer['ID']; + var uri = '../queries/' + answer['ID'] + '/answers'; + + $.ajax({ + url: uri, + dataType: 'json', + async: false, + success: function(answers) { + + var target = $('#query-retrieve-3 ul'); + $('li', target).remove(); + + for (var i = 0; i < answers.length; i++) { + $.ajax({ + url: uri + '/' + answers[i] + '/content?simplify', + dataType: 'json', + async: false, + success: function(series) { + var content = ($('<div>') + .append($('<h3>').text(series['SeriesDescription'])) + .append($('<p>').text('Modality: ') + .append($('<b>').text(series['Modality']))) + .append($('<p>').text('ProtocolName: ') + .append($('<b>').text(series['ProtocolName'])))); + + var info = $('<a>').html(content); + + var answerId = answers[i]; + info.click(function() { + ChangePage('query-retrieve-4', { + 'query' : queryUuid, + 'study' : pageData.uuid, + 'answer' : answerId, + 'server' : pageData.server + }); + }); + + target.append($('<li>').attr('data-icon', 'arrow-d').append(info)); + } + }); + } + + target.listview('refresh'); + } + }); + } + }); + } +}); + + + +$('#query-retrieve-4').live('pagebeforeshow', function() { + if ($.mobile.pageData) { + var pageData = DeepCopy($.mobile.pageData); + var uri = '../queries/' + pageData.query + '/answers/' + pageData.answer + '/retrieve'; + + $.ajax({ + url: '../system', + dataType: 'json', + async: false, + cache: false, + success: function(system) { + $('#retrieve-target').val(system['DicomAet']); + + $('#retrieve-form').submit(function(event) { + event.preventDefault(); + + var aet = $('#retrieve-target').val(); + if (aet.length == 0) { + aet = system['DicomAet']; + } + + $.ajax({ + url: uri, + type: 'POST', + async: true, // Necessary to block UI + dataType: 'text', + data: aet, + beforeSend: function() { + $.blockUI({ message: $('#info-retrieve') }); + }, + complete: function(s) { + $.unblockUI(); + }, + success: function() { + if (pageData.study) { + // Go back to the list of series + ChangePage('query-retrieve-3', { + 'server' : pageData.server, + 'uuid' : pageData.study + }); + } else { + // Go back to the list of studies + ChangePage('query-retrieve-2', { + 'server' : pageData.server, + 'uuid' : pageData.query + }); + } + }, + error: function() { + alert('Error during retrieve'); + } + }); + }); + } + }); + } +});
--- a/OrthancServer/DatabaseWrapper.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/DatabaseWrapper.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +36,6 @@ #include "../Core/DicomFormat/DicomArray.h" #include "../Core/Logging.h" -#include "../Core/Uuid.h" #include "EmbeddedResources.h" #include "ServerToolbox.h" @@ -44,7 +44,6 @@ namespace Orthanc { - namespace Internals { class SignalFileDeleted : public SQLite::IScalarFunction @@ -187,6 +186,59 @@ } + void DatabaseWrapper::GetChangesInternal(std::list<ServerIndexChange>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) + { + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); + ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); + const std::string& date = s.ColumnString(4); + + int64_t internalId = s.ColumnInt64(2); + std::string publicId = GetPublicId(internalId); + + target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); + } + + done = !(target.size() == maxResults && s.Step()); + } + + + void DatabaseWrapper::GetExportedResourcesInternal(std::list<ExportedResource>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) + { + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); + std::string publicId = s.ColumnString(2); + + ExportedResource resource(seq, + resourceType, + publicId, + s.ColumnString(3), // modality + s.ColumnString(8), // date + s.ColumnString(4), // patient ID + s.ColumnString(5), // study instance UID + s.ColumnString(6), // series instance UID + s.ColumnString(7)); // sop instance UID + + target.push_back(resource); + } + + done = !(target.size() == maxResults && s.Step()); + } + void DatabaseWrapper::GetChildren(std::list<std::string>& childrenPublicIds, int64_t id) @@ -256,16 +308,24 @@ } - DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL), base_(db_) + DatabaseWrapper::DatabaseWrapper(const std::string& path) : + listener_(NULL), + signalRemainingAncestor_(NULL), + version_(0) { db_.Open(path); } - DatabaseWrapper::DatabaseWrapper() : listener_(NULL), base_(db_) + + DatabaseWrapper::DatabaseWrapper() : + listener_(NULL), + signalRemainingAncestor_(NULL), + version_(0) { db_.OpenInMemory(); } + void DatabaseWrapper::Open() { db_.Execute("PRAGMA ENCODING=\"UTF-8\";"); @@ -365,10 +425,10 @@ // consists in reconstructing the main DICOM tags information // (as more tags got included). db_.BeginTransaction(); - Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Patient); - Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study); - Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series); - Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Instance); + ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Patient); + ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study); + ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series); + ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Instance); db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" + boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";"); db_.CommitTransaction(); @@ -394,43 +454,53 @@ bool DatabaseWrapper::LookupParent(int64_t& parentId, int64_t resourceId) { - bool found; - ErrorCode error = base_.LookupParent(found, parentId, resourceId); + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT parentId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); - if (error != ErrorCode_Success) + if (!s.Step()) { - throw OrthancException(error); + throw OrthancException(ErrorCode_UnknownResource); + } + + if (s.ColumnIsNull(0)) + { + return false; } else { - return found; + parentId = s.ColumnInt(0); + return true; } } ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId) { - ResourceType result; - ErrorCode code = base_.GetResourceType(result, resourceId); - - if (code == ErrorCode_Success) + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT resourceType FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (s.Step()) { - return result; + return static_cast<ResourceType>(s.ColumnInt(0)); } else - { - throw OrthancException(code); + { + throw OrthancException(ErrorCode_UnknownResource); } } std::string DatabaseWrapper::GetPublicId(int64_t resourceId) { - std::string id; - - if (base_.GetPublicId(id, resourceId)) - { - return id; + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT publicId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (s.Step()) + { + return s.ColumnString(0); } else { @@ -444,23 +514,18 @@ int64_t since, uint32_t maxResults) { - ErrorCode code = base_.GetChanges(target, done, since, maxResults); - - if (code != ErrorCode_Success) - { - throw OrthancException(code); - } + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + GetChangesInternal(target, done, s, maxResults); } void DatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target /*out*/) { - ErrorCode code = base_.GetLastChange(target); - - if (code != ErrorCode_Success) - { - throw OrthancException(code); - } + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); + GetChangesInternal(target, done, s, 1); } @@ -479,4 +544,588 @@ } } + + void DatabaseWrapper::SetGlobalProperty(GlobalProperty property, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); + s.BindInt(0, property); + s.BindString(1, value); + s.Run(); + } + + + bool DatabaseWrapper::LookupGlobalProperty(std::string& target, + GlobalProperty property) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM GlobalProperties WHERE property=?"); + s.BindInt(0, property); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + + int64_t DatabaseWrapper::CreateResource(const std::string& publicId, + ResourceType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); + s.BindInt(0, type); + s.BindString(1, publicId); + s.Run(); + return db_.GetLastInsertRowId(); + } + + + bool DatabaseWrapper::LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); + s.BindString(0, publicId); + + if (!s.Step()) + { + return false; + } + else + { + id = s.ColumnInt(0); + type = static_cast<ResourceType>(s.ColumnInt(1)); + + // Check whether there is a single resource with this public id + assert(!s.Step()); + + return true; + } + } + + + void DatabaseWrapper::AttachChild(int64_t parent, + int64_t child) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); + s.BindInt64(0, parent); + s.BindInt64(1, child); + s.Run(); + } + + + void DatabaseWrapper::SetMetadata(int64_t id, + MetadataType type, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.BindString(2, value); + s.Run(); + } + + + void DatabaseWrapper::DeleteMetadata(int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.Run(); + } + + + bool DatabaseWrapper::LookupMetadata(std::string& target, + int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM Metadata WHERE id=? AND type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + + void DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.push_back(static_cast<MetadataType>(s.ColumnInt(0))); + } + } + + + void DatabaseWrapper::AddAttachment(int64_t id, + const FileInfo& attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, attachment.GetContentType()); + s.BindString(2, attachment.GetUuid()); + s.BindInt64(3, attachment.GetCompressedSize()); + s.BindInt64(4, attachment.GetUncompressedSize()); + s.BindInt(5, attachment.GetCompressionType()); + s.BindString(6, attachment.GetUncompressedMD5()); + s.BindString(7, attachment.GetCompressedMD5()); + s.Run(); + } + + + void DatabaseWrapper::DeleteAttachment(int64_t id, + FileContentType attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, attachment); + s.Run(); + } + + + void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT fileType FROM AttachedFiles WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.push_back(static_cast<FileContentType>(s.ColumnInt(0))); + } + } + + bool DatabaseWrapper::LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT uuid, uncompressedSize, compressionType, compressedSize, " + "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, contentType); + + if (!s.Step()) + { + return false; + } + else + { + attachment = FileInfo(s.ColumnString(0), + contentType, + s.ColumnInt64(1), + s.ColumnString(4), + static_cast<CompressionType>(s.ColumnInt(2)), + s.ColumnInt64(3), + s.ColumnString(5)); + return true; + } + } + + + void DatabaseWrapper::ClearMainDicomTags(int64_t id) + { + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + } + + + void DatabaseWrapper::SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); + s.Run(); + } + + + void DatabaseWrapper::SetIdentifierTag(int64_t id, + const DicomTag& tag, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); + s.Run(); + } + + + void DatabaseWrapper::GetMainDicomTags(DicomMap& map, + int64_t id) + { + map.Clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + while (s.Step()) + { + map.SetValue(s.ColumnInt(1), + s.ColumnInt(2), + s.ColumnString(3), false); + } + } + + + void DatabaseWrapper::GetChildrenPublicId(std::list<std::string>& target, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + void DatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& target, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapper::LogChange(int64_t internalId, + const ServerIndexChange& change) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); + s.BindInt(0, change.GetChangeType()); + s.BindInt64(1, internalId); + s.BindInt(2, change.GetResourceType()); + s.BindString(3, change.GetDate()); + s.Run(); + } + + + void DatabaseWrapper::LogExportedResource(const ExportedResource& resource) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); + + s.BindInt(0, resource.GetResourceType()); + s.BindString(1, resource.GetPublicId()); + s.BindString(2, resource.GetModality()); + s.BindString(3, resource.GetPatientId()); + s.BindString(4, resource.GetStudyInstanceUid()); + s.BindString(5, resource.GetSeriesInstanceUid()); + s.BindString(6, resource.GetSopInstanceUid()); + s.BindString(7, resource.GetDate()); + s.Run(); + } + + + void DatabaseWrapper::GetExportedResources(std::list<ExportedResource>& target, + bool& done, + int64_t since, + uint32_t maxResults) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + GetExportedResourcesInternal(target, done, s, maxResults); + } + + + void DatabaseWrapper::GetLastExportedResource(std::list<ExportedResource>& target) + { + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); + GetExportedResourcesInternal(target, done, s, 1); + } + + + uint64_t DatabaseWrapper::GetTotalCompressedSize() + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); + s.Run(); + return static_cast<uint64_t>(s.ColumnInt64(0)); + } + + + uint64_t DatabaseWrapper::GetTotalUncompressedSize() + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); + s.Run(); + return static_cast<uint64_t>(s.ColumnInt64(0)); + } + + + uint64_t DatabaseWrapper::GetResourceCount(ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + if (!s.Step()) + { + return 0; + } + else + { + int64_t c = s.ColumnInt(0); + assert(!s.Step()); + return c; + } + } + + + void DatabaseWrapper::GetAllInternalIds(std::list<int64_t>& target, + ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT internalId FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapper::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + void DatabaseWrapper::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) + { + if (limit == 0) + { + target.clear(); + return; + } + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT publicId FROM Resources WHERE " + "resourceType=? LIMIT ? OFFSET ?"); + s.BindInt(0, resourceType); + s.BindInt64(1, limit); + s.BindInt64(2, since); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + + bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder " + "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); + s.BindInt64(0, patientIdToAvoid); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + + bool DatabaseWrapper::IsProtectedPatient(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); + s.BindInt64(0, internalId); + return !s.Step(); + } + + + void DatabaseWrapper::SetProtectedPatient(int64_t internalId, + bool isProtected) + { + if (isProtected) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); + s.BindInt64(0, internalId); + s.Run(); + } + else if (IsProtectedPatient(internalId)) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); + s.BindInt64(0, internalId); + s.Run(); + } + else + { + // Nothing to do: The patient is already unprotected + } + } + + + bool DatabaseWrapper::IsExistingResource(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM Resources WHERE internalId=?"); + s.BindInt64(0, internalId); + return s.Step(); + } + + + void DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target, + ResourceType level, + const DicomTag& tag, + IdentifierConstraintType type, + const std::string& value) + { + static const char* COMMON = ("SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " + "d.id = r.internalId AND r.resourceType=? AND " + "d.tagGroup=? AND d.tagElement=? AND "); + + std::auto_ptr<SQLite::Statement> s; + + switch (type) + { + case IdentifierConstraintType_GreaterOrEqual: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value>=?")); + break; + + case IdentifierConstraintType_SmallerOrEqual: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value<=?")); + break; + + case IdentifierConstraintType_Wildcard: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value GLOB ?")); + break; + + case IdentifierConstraintType_Equal: + default: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value=?")); + break; + } + + assert(s.get() != NULL); + + s->BindInt(0, level); + s->BindInt(1, tag.GetGroup()); + s->BindInt(2, tag.GetElement()); + s->BindString(3, value); + + target.clear(); + + while (s->Step()) + { + target.push_back(s->ColumnInt64(0)); + } + } + + + void DatabaseWrapper::LookupIdentifierRange(std::list<int64_t>& target, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " + "d.id = r.internalId AND r.resourceType=? AND " + "d.tagGroup=? AND d.tagElement=? AND d.value>=? AND d.value<=?"); + + statement.BindInt(0, level); + statement.BindInt(1, tag.GetGroup()); + statement.BindInt(2, tag.GetElement()); + statement.BindString(3, start); + statement.BindString(4, end); + + target.clear(); + + while (statement.Step()) + { + target.push_back(statement.ColumnInt64(0)); + } + } }
--- a/OrthancServer/DatabaseWrapper.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/DatabaseWrapper.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +37,6 @@ #include "../Core/SQLite/Connection.h" #include "../Core/SQLite/Transaction.h" -#include "DatabaseWrapperBase.h" namespace Orthanc { @@ -55,10 +55,19 @@ private: IDatabaseListener* listener_; SQLite::Connection db_; - DatabaseWrapperBase base_; Internals::SignalRemainingAncestor* signalRemainingAncestor_; unsigned int version_; + void GetChangesInternal(std::list<ServerIndexChange>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults); + + void GetExportedResourcesInternal(std::list<ExportedResource>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults); + void ClearTable(const std::string& tableName); public: @@ -75,31 +84,6 @@ virtual void SetListener(IDatabaseListener& listener); - virtual void SetGlobalProperty(GlobalProperty property, - const std::string& value) - { - base_.SetGlobalProperty(property, value); - } - - virtual bool LookupGlobalProperty(std::string& target, - GlobalProperty property) - { - return base_.LookupGlobalProperty(target, property); - } - - virtual int64_t CreateResource(const std::string& publicId, - ResourceType type) - { - return base_.CreateResource(publicId, type); - } - - virtual bool LookupResource(int64_t& id, - ResourceType& type, - const std::string& publicId) - { - return base_.LookupResource(id, type, publicId); - } - virtual bool LookupParent(int64_t& parentId, int64_t resourceId); @@ -107,108 +91,8 @@ virtual ResourceType GetResourceType(int64_t resourceId); - virtual void AttachChild(int64_t parent, - int64_t child) - { - base_.AttachChild(parent, child); - } - virtual void DeleteResource(int64_t id); - virtual void SetMetadata(int64_t id, - MetadataType type, - const std::string& value) - { - base_.SetMetadata(id, type, value); - } - - virtual void DeleteMetadata(int64_t id, - MetadataType type) - { - base_.DeleteMetadata(id, type); - } - - virtual bool LookupMetadata(std::string& target, - int64_t id, - MetadataType type) - { - return base_.LookupMetadata(target, id, type); - } - - virtual void ListAvailableMetadata(std::list<MetadataType>& target, - int64_t id) - { - base_.ListAvailableMetadata(target, id); - } - - virtual void AddAttachment(int64_t id, - const FileInfo& attachment) - { - base_.AddAttachment(id, attachment); - } - - virtual void DeleteAttachment(int64_t id, - FileContentType attachment) - { - base_.DeleteAttachment(id, attachment); - } - - virtual void ListAvailableAttachments(std::list<FileContentType>& target, - int64_t id) - { - return base_.ListAvailableAttachments(target, id); - } - - virtual bool LookupAttachment(FileInfo& attachment, - int64_t id, - FileContentType contentType) - { - return base_.LookupAttachment(attachment, id, contentType); - } - - virtual void ClearMainDicomTags(int64_t id) - { - base_.ClearMainDicomTags(id); - } - - virtual void SetMainDicomTag(int64_t id, - const DicomTag& tag, - const std::string& value) - { - base_.SetMainDicomTag(id, tag, value); - } - - virtual void SetIdentifierTag(int64_t id, - const DicomTag& tag, - const std::string& value) - { - base_.SetIdentifierTag(id, tag, value); - } - - virtual void GetMainDicomTags(DicomMap& map, - int64_t id) - { - base_.GetMainDicomTags(map, id); - } - - virtual void GetChildrenPublicId(std::list<std::string>& target, - int64_t id) - { - base_.GetChildrenPublicId(target, id); - } - - virtual void GetChildrenInternalId(std::list<int64_t>& target, - int64_t id) - { - base_.GetChildrenInternalId(target, id); - } - - virtual void LogChange(int64_t internalId, - const ServerIndexChange& change) - { - base_.LogChange(internalId, change); - } - virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/, bool& done /*out*/, int64_t since, @@ -216,81 +100,6 @@ virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/); - virtual void LogExportedResource(const ExportedResource& resource) - { - base_.LogExportedResource(resource); - } - - virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/, - bool& done /*out*/, - int64_t since, - uint32_t maxResults) - { - base_.GetExportedResources(target, done, since, maxResults); - } - - virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) - { - base_.GetLastExportedResource(target); - } - - virtual uint64_t GetTotalCompressedSize() - { - return base_.GetTotalCompressedSize(); - } - - virtual uint64_t GetTotalUncompressedSize() - { - return base_.GetTotalUncompressedSize(); - } - - virtual uint64_t GetResourceCount(ResourceType resourceType) - { - return base_.GetResourceCount(resourceType); - } - - virtual void GetAllInternalIds(std::list<int64_t>& target, - ResourceType resourceType) - { - base_.GetAllInternalIds(target, resourceType); - } - - virtual void GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType) - { - base_.GetAllPublicIds(target, resourceType); - } - - virtual void GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType, - size_t since, - size_t limit) - { - base_.GetAllPublicIds(target, resourceType, since, limit); - } - - virtual bool SelectPatientToRecycle(int64_t& internalId) - { - return base_.SelectPatientToRecycle(internalId); - } - - virtual bool SelectPatientToRecycle(int64_t& internalId, - int64_t patientIdToAvoid) - { - return base_.SelectPatientToRecycle(internalId, patientIdToAvoid); - } - - virtual bool IsProtectedPatient(int64_t internalId) - { - return base_.IsProtectedPatient(internalId); - } - - virtual void SetProtectedPatient(int64_t internalId, - bool isProtected) - { - base_.SetProtectedPatient(internalId, isProtected); - } - virtual SQLite::ITransaction* StartTransaction() { return new SQLite::Transaction(db_); @@ -316,20 +125,6 @@ ClearTable("ExportedResources"); } - virtual bool IsExistingResource(int64_t internalId) - { - return base_.IsExistingResource(internalId); - } - - virtual void LookupIdentifier(std::list<int64_t>& result, - ResourceType level, - const DicomTag& tag, - IdentifierConstraintType type, - const std::string& value) - { - base_.LookupIdentifier(result, level, tag, type, value); - } - virtual void GetAllMetadata(std::map<MetadataType, std::string>& target, int64_t id); @@ -342,7 +137,6 @@ IStorageArea& storageArea); - /** * The methods declared below are for unit testing only! **/ @@ -360,5 +154,127 @@ bool GetParentPublicId(std::string& target, int64_t id); + + + /** + * Until Orthanc 1.4.0, the methods below were part of the + * "DatabaseWrapperBase" class, that is now placed in the + * graveyard. + **/ + + virtual void SetGlobalProperty(GlobalProperty property, + const std::string& value); + + virtual bool LookupGlobalProperty(std::string& target, + GlobalProperty property); + + virtual int64_t CreateResource(const std::string& publicId, + ResourceType type); + + virtual bool LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId); + + virtual void AttachChild(int64_t parent, + int64_t child); + + virtual void SetMetadata(int64_t id, + MetadataType type, + const std::string& value); + + virtual void DeleteMetadata(int64_t id, + MetadataType type); + + virtual bool LookupMetadata(std::string& target, + int64_t id, + MetadataType type); + + virtual void ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id); + + virtual void AddAttachment(int64_t id, + const FileInfo& attachment); + + virtual void DeleteAttachment(int64_t id, + FileContentType attachment); + + virtual void ListAvailableAttachments(std::list<FileContentType>& target, + int64_t id); + + virtual bool LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType); + + virtual void ClearMainDicomTags(int64_t id); + + virtual void SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value); + + virtual void SetIdentifierTag(int64_t id, + const DicomTag& tag, + const std::string& value); + + virtual void GetMainDicomTags(DicomMap& map, + int64_t id); + + virtual void GetChildrenPublicId(std::list<std::string>& target, + int64_t id); + + virtual void GetChildrenInternalId(std::list<int64_t>& target, + int64_t id); + + virtual void LogChange(int64_t internalId, + const ServerIndexChange& change); + + virtual void LogExportedResource(const ExportedResource& resource); + + virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/, + bool& done /*out*/, + int64_t since, + uint32_t maxResults); + + virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/); + + virtual uint64_t GetTotalCompressedSize(); + + virtual uint64_t GetTotalUncompressedSize(); + + virtual uint64_t GetResourceCount(ResourceType resourceType); + + virtual void GetAllInternalIds(std::list<int64_t>& target, + ResourceType resourceType); + + virtual void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType); + + virtual void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit); + + virtual bool SelectPatientToRecycle(int64_t& internalId); + + virtual bool SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid); + + virtual bool IsProtectedPatient(int64_t internalId); + + virtual void SetProtectedPatient(int64_t internalId, + bool isProtected); + + virtual bool IsExistingResource(int64_t internalId); + + virtual void LookupIdentifier(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + IdentifierConstraintType type, + const std::string& value); + + virtual void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end); }; }
--- a/OrthancServer/DatabaseWrapperBase.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,730 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersServer.h" -#include "DatabaseWrapperBase.h" - -#include <stdio.h> -#include <memory> - -namespace Orthanc -{ - void DatabaseWrapperBase::SetGlobalProperty(GlobalProperty property, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); - s.BindInt(0, property); - s.BindString(1, value); - s.Run(); - } - - bool DatabaseWrapperBase::LookupGlobalProperty(std::string& target, - GlobalProperty property) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT value FROM GlobalProperties WHERE property=?"); - s.BindInt(0, property); - - if (!s.Step()) - { - return false; - } - else - { - target = s.ColumnString(0); - return true; - } - } - - int64_t DatabaseWrapperBase::CreateResource(const std::string& publicId, - ResourceType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); - s.BindInt(0, type); - s.BindString(1, publicId); - s.Run(); - return db_.GetLastInsertRowId(); - } - - bool DatabaseWrapperBase::LookupResource(int64_t& id, - ResourceType& type, - const std::string& publicId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); - s.BindString(0, publicId); - - if (!s.Step()) - { - return false; - } - else - { - id = s.ColumnInt(0); - type = static_cast<ResourceType>(s.ColumnInt(1)); - - // Check whether there is a single resource with this public id - assert(!s.Step()); - - return true; - } - } - - ErrorCode DatabaseWrapperBase::LookupParent(bool& found, - int64_t& parentId, - int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT parentId FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (!s.Step()) - { - return ErrorCode_UnknownResource; - } - - if (s.ColumnIsNull(0)) - { - found = false; - } - else - { - found = true; - parentId = s.ColumnInt(0); - } - - return ErrorCode_Success; - } - - bool DatabaseWrapperBase::GetPublicId(std::string& result, - int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT publicId FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (!s.Step()) - { - return false; - } - else - { - result = s.ColumnString(0); - return true; - } - } - - - ErrorCode DatabaseWrapperBase::GetResourceType(ResourceType& result, - int64_t resourceId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT resourceType FROM Resources WHERE internalId=?"); - s.BindInt64(0, resourceId); - - if (s.Step()) - { - result = static_cast<ResourceType>(s.ColumnInt(0)); - return ErrorCode_Success; - } - else - { - return ErrorCode_UnknownResource; - } - } - - - void DatabaseWrapperBase::AttachChild(int64_t parent, - int64_t child) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); - s.BindInt64(0, parent); - s.BindInt64(1, child); - s.Run(); - } - - - void DatabaseWrapperBase::SetMetadata(int64_t id, - MetadataType type, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, type); - s.BindString(2, value); - s.Run(); - } - - void DatabaseWrapperBase::DeleteMetadata(int64_t id, - MetadataType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); - s.BindInt64(0, id); - s.BindInt(1, type); - s.Run(); - } - - bool DatabaseWrapperBase::LookupMetadata(std::string& target, - int64_t id, - MetadataType type) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT value FROM Metadata WHERE id=? AND type=?"); - s.BindInt64(0, id); - s.BindInt(1, type); - - if (!s.Step()) - { - return false; - } - else - { - target = s.ColumnString(0); - return true; - } - } - - void DatabaseWrapperBase::ListAvailableMetadata(std::list<MetadataType>& target, - int64_t id) - { - target.clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?"); - s.BindInt64(0, id); - - while (s.Step()) - { - target.push_back(static_cast<MetadataType>(s.ColumnInt(0))); - } - } - - - void DatabaseWrapperBase::AddAttachment(int64_t id, - const FileInfo& attachment) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, attachment.GetContentType()); - s.BindString(2, attachment.GetUuid()); - s.BindInt64(3, attachment.GetCompressedSize()); - s.BindInt64(4, attachment.GetUncompressedSize()); - s.BindInt(5, attachment.GetCompressionType()); - s.BindString(6, attachment.GetUncompressedMD5()); - s.BindString(7, attachment.GetCompressedMD5()); - s.Run(); - } - - - void DatabaseWrapperBase::DeleteAttachment(int64_t id, - FileContentType attachment) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); - s.BindInt64(0, id); - s.BindInt(1, attachment); - s.Run(); - } - - - - void DatabaseWrapperBase::ListAvailableAttachments(std::list<FileContentType>& target, - int64_t id) - { - target.clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT fileType FROM AttachedFiles WHERE id=?"); - s.BindInt64(0, id); - - while (s.Step()) - { - target.push_back(static_cast<FileContentType>(s.ColumnInt(0))); - } - } - - bool DatabaseWrapperBase::LookupAttachment(FileInfo& attachment, - int64_t id, - FileContentType contentType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); - s.BindInt64(0, id); - s.BindInt(1, contentType); - - if (!s.Step()) - { - return false; - } - else - { - attachment = FileInfo(s.ColumnString(0), - contentType, - s.ColumnInt64(1), - s.ColumnString(4), - static_cast<CompressionType>(s.ColumnInt(2)), - s.ColumnInt64(3), - s.ColumnString(5)); - return true; - } - } - - - void DatabaseWrapperBase::ClearMainDicomTags(int64_t id) - { - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?"); - s.BindInt64(0, id); - s.Run(); - } - - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?"); - s.BindInt64(0, id); - s.Run(); - } - } - - - void DatabaseWrapperBase::SetMainDicomTag(int64_t id, - const DicomTag& tag, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, tag.GetGroup()); - s.BindInt(2, tag.GetElement()); - s.BindString(3, value); - s.Run(); - } - - - void DatabaseWrapperBase::SetIdentifierTag(int64_t id, - const DicomTag& tag, - const std::string& value) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); - s.BindInt64(0, id); - s.BindInt(1, tag.GetGroup()); - s.BindInt(2, tag.GetElement()); - s.BindString(3, value); - s.Run(); - } - - - void DatabaseWrapperBase::GetMainDicomTags(DicomMap& map, - int64_t id) - { - map.Clear(); - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); - s.BindInt64(0, id); - while (s.Step()) - { - map.SetValue(s.ColumnInt(1), - s.ColumnInt(2), - s.ColumnString(3)); - } - } - - - - void DatabaseWrapperBase::GetChildrenPublicId(std::list<std::string>& target, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " - "WHERE a.parentId = b.internalId AND b.internalId = ?"); - s.BindInt64(0, id); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - - void DatabaseWrapperBase::GetChildrenInternalId(std::list<int64_t>& target, - int64_t id) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " - "WHERE a.parentId = b.internalId AND b.internalId = ?"); - s.BindInt64(0, id); - - target.clear(); - - while (s.Step()) - { - target.push_back(s.ColumnInt64(0)); - } - } - - - void DatabaseWrapperBase::LogChange(int64_t internalId, - const ServerIndexChange& change) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); - s.BindInt(0, change.GetChangeType()); - s.BindInt64(1, internalId); - s.BindInt(2, change.GetResourceType()); - s.BindString(3, change.GetDate()); - s.Run(); - } - - - ErrorCode DatabaseWrapperBase::GetChangesInternal(std::list<ServerIndexChange>& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults) - { - target.clear(); - - while (target.size() < maxResults && s.Step()) - { - int64_t seq = s.ColumnInt64(0); - ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); - ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); - const std::string& date = s.ColumnString(4); - - int64_t internalId = s.ColumnInt64(2); - std::string publicId; - if (!GetPublicId(publicId, internalId)) - { - return ErrorCode_UnknownResource; - } - - target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); - } - - done = !(target.size() == maxResults && s.Step()); - return ErrorCode_Success; - } - - - ErrorCode DatabaseWrapperBase::GetChanges(std::list<ServerIndexChange>& target, - bool& done, - int64_t since, - uint32_t maxResults) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt64(0, since); - s.BindInt(1, maxResults + 1); - return GetChangesInternal(target, done, s, maxResults); - } - - ErrorCode DatabaseWrapperBase::GetLastChange(std::list<ServerIndexChange>& target) - { - bool done; // Ignored - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); - return GetChangesInternal(target, done, s, 1); - } - - - void DatabaseWrapperBase::LogExportedResource(const ExportedResource& resource) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); - - s.BindInt(0, resource.GetResourceType()); - s.BindString(1, resource.GetPublicId()); - s.BindString(2, resource.GetModality()); - s.BindString(3, resource.GetPatientId()); - s.BindString(4, resource.GetStudyInstanceUid()); - s.BindString(5, resource.GetSeriesInstanceUid()); - s.BindString(6, resource.GetSopInstanceUid()); - s.BindString(7, resource.GetDate()); - s.Run(); - } - - - void DatabaseWrapperBase::GetExportedResourcesInternal(std::list<ExportedResource>& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults) - { - target.clear(); - - while (target.size() < maxResults && s.Step()) - { - int64_t seq = s.ColumnInt64(0); - ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); - std::string publicId = s.ColumnString(2); - - ExportedResource resource(seq, - resourceType, - publicId, - s.ColumnString(3), // modality - s.ColumnString(8), // date - s.ColumnString(4), // patient ID - s.ColumnString(5), // study instance UID - s.ColumnString(6), // series instance UID - s.ColumnString(7)); // sop instance UID - - target.push_back(resource); - } - - done = !(target.size() == maxResults && s.Step()); - } - - - void DatabaseWrapperBase::GetExportedResources(std::list<ExportedResource>& target, - bool& done, - int64_t since, - uint32_t maxResults) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); - s.BindInt64(0, since); - s.BindInt(1, maxResults + 1); - GetExportedResourcesInternal(target, done, s, maxResults); - } - - - void DatabaseWrapperBase::GetLastExportedResource(std::list<ExportedResource>& target) - { - bool done; // Ignored - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); - GetExportedResourcesInternal(target, done, s, 1); - } - - - - uint64_t DatabaseWrapperBase::GetTotalCompressedSize() - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); - s.Run(); - return static_cast<uint64_t>(s.ColumnInt64(0)); - } - - - uint64_t DatabaseWrapperBase::GetTotalUncompressedSize() - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); - s.Run(); - return static_cast<uint64_t>(s.ColumnInt64(0)); - } - - void DatabaseWrapperBase::GetAllInternalIds(std::list<int64_t>& target, - ResourceType resourceType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT internalId FROM Resources WHERE resourceType=?"); - s.BindInt(0, resourceType); - - target.clear(); - while (s.Step()) - { - target.push_back(s.ColumnInt64(0)); - } - } - - - void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); - s.BindInt(0, resourceType); - - target.clear(); - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType, - size_t since, - size_t limit) - { - if (limit == 0) - { - target.clear(); - return; - } - - SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?"); - s.BindInt(0, resourceType); - s.BindInt64(1, limit); - s.BindInt64(2, since); - - target.clear(); - while (s.Step()) - { - target.push_back(s.ColumnString(0)); - } - } - - - uint64_t DatabaseWrapperBase::GetResourceCount(ResourceType resourceType) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); - s.BindInt(0, resourceType); - - if (!s.Step()) - { - return 0; - } - else - { - int64_t c = s.ColumnInt(0); - assert(!s.Step()); - return c; - } - } - - - bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); - - if (!s.Step()) - { - // No patient remaining or all the patients are protected - return false; - } - else - { - internalId = s.ColumnInt(0); - return true; - } - } - - bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId, - int64_t patientIdToAvoid) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT patientId FROM PatientRecyclingOrder " - "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); - s.BindInt64(0, patientIdToAvoid); - - if (!s.Step()) - { - // No patient remaining or all the patients are protected - return false; - } - else - { - internalId = s.ColumnInt(0); - return true; - } - } - - bool DatabaseWrapperBase::IsProtectedPatient(int64_t internalId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); - s.BindInt64(0, internalId); - return !s.Step(); - } - - void DatabaseWrapperBase::SetProtectedPatient(int64_t internalId, - bool isProtected) - { - if (isProtected) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); - s.BindInt64(0, internalId); - s.Run(); - } - else if (IsProtectedPatient(internalId)) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); - s.BindInt64(0, internalId); - s.Run(); - } - else - { - // Nothing to do: The patient is already unprotected - } - } - - - - bool DatabaseWrapperBase::IsExistingResource(int64_t internalId) - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT * FROM Resources WHERE internalId=?"); - s.BindInt64(0, internalId); - return s.Step(); - } - - - - void DatabaseWrapperBase::LookupIdentifier(std::list<int64_t>& target, - ResourceType level, - const DicomTag& tag, - IdentifierConstraintType type, - const std::string& value) - { - static const char* COMMON = ("SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " - "d.id = r.internalId AND r.resourceType=? AND " - "d.tagGroup=? AND d.tagElement=? AND "); - - std::auto_ptr<SQLite::Statement> s; - - switch (type) - { - case IdentifierConstraintType_GreaterOrEqual: - s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value>=?")); - break; - - case IdentifierConstraintType_SmallerOrEqual: - s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value<=?")); - break; - - case IdentifierConstraintType_Wildcard: - s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value GLOB ?")); - break; - - case IdentifierConstraintType_Equal: - default: - s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value=?")); - break; - } - - assert(s.get() != NULL); - - s->BindInt(0, level); - s->BindInt(1, tag.GetGroup()); - s->BindInt(2, tag.GetElement()); - s->BindString(3, value); - - target.clear(); - - while (s->Step()) - { - target.push_back(s->ColumnInt64(0)); - } - } -}
--- a/OrthancServer/DatabaseWrapperBase.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Core/DicomFormat/DicomMap.h" -#include "../Core/DicomFormat/DicomTag.h" -#include "../Core/Enumerations.h" -#include "../Core/FileStorage/FileInfo.h" -#include "../Core/SQLite/Connection.h" -#include "../OrthancServer/ExportedResource.h" -#include "../OrthancServer/ServerIndexChange.h" -#include "ServerEnumerations.h" - -#include <list> - - -namespace Orthanc -{ - /** - * This class is shared between the Orthanc core and the sample - * database plugin whose code is in - * "../Plugins/Samples/DatabasePlugin". - **/ - class DatabaseWrapperBase - { - private: - SQLite::Connection& db_; - - ErrorCode GetChangesInternal(std::list<ServerIndexChange>& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults); - - void GetExportedResourcesInternal(std::list<ExportedResource>& target, - bool& done, - SQLite::Statement& s, - uint32_t maxResults); - - public: - DatabaseWrapperBase(SQLite::Connection& db) : db_(db) - { - } - - void SetGlobalProperty(GlobalProperty property, - const std::string& value); - - bool LookupGlobalProperty(std::string& target, - GlobalProperty property); - - int64_t CreateResource(const std::string& publicId, - ResourceType type); - - bool LookupResource(int64_t& id, - ResourceType& type, - const std::string& publicId); - - ErrorCode LookupParent(bool& found, - int64_t& parentId, - int64_t resourceId); - - bool GetPublicId(std::string& result, - int64_t resourceId); - - ErrorCode GetResourceType(ResourceType& result, - int64_t resourceId); - - void AttachChild(int64_t parent, - int64_t child); - - void SetMetadata(int64_t id, - MetadataType type, - const std::string& value); - - void DeleteMetadata(int64_t id, - MetadataType type); - - bool LookupMetadata(std::string& target, - int64_t id, - MetadataType type); - - void ListAvailableMetadata(std::list<MetadataType>& target, - int64_t id); - - void AddAttachment(int64_t id, - const FileInfo& attachment); - - void DeleteAttachment(int64_t id, - FileContentType attachment); - - void ListAvailableAttachments(std::list<FileContentType>& target, - int64_t id); - - bool LookupAttachment(FileInfo& attachment, - int64_t id, - FileContentType contentType); - - - void ClearMainDicomTags(int64_t id); - - - void SetMainDicomTag(int64_t id, - const DicomTag& tag, - const std::string& value); - - void SetIdentifierTag(int64_t id, - const DicomTag& tag, - const std::string& value); - - void GetMainDicomTags(DicomMap& map, - int64_t id); - - void GetChildrenPublicId(std::list<std::string>& target, - int64_t id); - - void GetChildrenInternalId(std::list<int64_t>& target, - int64_t id); - - void LogChange(int64_t internalId, - const ServerIndexChange& change); - - ErrorCode GetChanges(std::list<ServerIndexChange>& target, - bool& done, - int64_t since, - uint32_t maxResults); - - ErrorCode GetLastChange(std::list<ServerIndexChange>& target); - - void LogExportedResource(const ExportedResource& resource); - - void GetExportedResources(std::list<ExportedResource>& target, - bool& done, - int64_t since, - uint32_t maxResults); - - void GetLastExportedResource(std::list<ExportedResource>& target); - - uint64_t GetTotalCompressedSize(); - - uint64_t GetTotalUncompressedSize(); - - void GetAllInternalIds(std::list<int64_t>& target, - ResourceType resourceType); - - void GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType); - - void GetAllPublicIds(std::list<std::string>& target, - ResourceType resourceType, - size_t since, - size_t limit); - - uint64_t GetResourceCount(ResourceType resourceType); - - bool SelectPatientToRecycle(int64_t& internalId); - - bool SelectPatientToRecycle(int64_t& internalId, - int64_t patientIdToAvoid); - - bool IsProtectedPatient(int64_t internalId); - - void SetProtectedPatient(int64_t internalId, - bool isProtected); - - bool IsExistingResource(int64_t internalId); - - void LookupIdentifier(std::list<int64_t>& result, - ResourceType level, - const DicomTag& tag, - IdentifierConstraintType type, - const std::string& value); - }; -} -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DefaultDicomImageDecoder.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IDicomImageDecoder.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" +#include "../Core/DicomParsing/Internals/DicomImageDecoder.h" + +namespace Orthanc +{ + class DefaultDicomImageDecoder : public IDicomImageDecoder + { + public: + virtual ImageAccessor* Decode(const void* dicom, + size_t size, + unsigned int frame) + { + ParsedDicomFile parsed(dicom, size); + return DicomImageDecoder::Decode(parsed, frame); + } + }; +}
--- a/OrthancServer/DicomDirWriter.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,560 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - - -/*** - - Validation: - - # sudo apt-get install dicom3tools - # dciodvfy DICOMDIR 2>&1 | less - # dcentvfy DICOMDIR 2>&1 | less - - http://www.dclunie.com/dicom3tools/dciodvfy.html - - DICOMDIR viewer working with Wine under Linux: - http://www.microdicom.com/ - - ***/ - - -#include "PrecompiledHeadersServer.h" -#include "DicomDirWriter.h" - -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" - -#include "../Core/Logging.h" -#include "../Core/OrthancException.h" -#include "../Core/Uuid.h" - -#include <dcmtk/dcmdata/dcdicdir.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcddirif.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcsequen.h> -#include <dcmtk/dcmdata/dcostrmf.h> -#include "dcmtk/dcmdata/dcvrda.h" /* for class DcmDate */ -#include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ - -#include <memory> - -namespace Orthanc -{ - class DicomDirWriter::PImpl - { - private: - std::string fileSetId_; - Toolbox::TemporaryFile file_; - std::auto_ptr<DcmDicomDir> dir_; - - typedef std::pair<ResourceType, std::string> IndexKey; - typedef std::map<IndexKey, DcmDirectoryRecord* > Index; - Index index_; - - - /******************************************************************************* - * Functions adapted from "dcmdata/libsrc/dcddirif.cc" from DCMTK 3.6.0 - *******************************************************************************/ - - // print an error message to the console (stderr) that something went wrong with an attribute - static void printAttributeErrorMessage(const DcmTagKey &key, - const OFCondition &error, - const char *operation) - { - if (error.bad()) - { - OFString str; - if (operation != NULL) - { - str = "cannot "; - str += operation; - str += " "; - } - LOG(ERROR) << error.text() << ": " << str << DcmTag(key).getTagName() << " " << key; - } - } - - // copy element from dataset to directory record - static void copyElement(DcmItem& dataset, - const DcmTagKey &key, - DcmDirectoryRecord& record, - const OFBool optional, - const OFBool copyEmpty) - { - /* check whether tag exists in source dataset (if optional) */ - if (!optional || (copyEmpty && dataset.tagExists(key)) || dataset.tagExistsWithValue(key)) - { - DcmElement *delem = NULL; - /* get copy of element from source dataset */ - OFCondition status = dataset.findAndGetElement(key, delem, OFFalse /*searchIntoSub*/, OFTrue /*createCopy*/); - if (status.good()) - { - /* ... and insert it into the destination dataset (record) */ - status = record.insert(delem, OFTrue /*replaceOld*/); - if (status.good()) - { - DcmTag tag(key); - /* check for correct VR in the dataset */ - if (delem->getVR() != tag.getEVR()) - { - /* create warning message */ - LOG(WARNING) << "DICOMDIR: possibly wrong VR: " - << tag.getTagName() << " " << key << " with " - << DcmVR(delem->getVR()).getVRName() << " found, expected " - << tag.getVRName() << " instead"; - } - } else - delete delem; - } else if (status == EC_TagNotFound) - status = record.insertEmptyElement(key); - printAttributeErrorMessage(key, status, "insert"); - } - } - - // copy optional string value from dataset to directory record - static void copyStringWithDefault(DcmItem& dataset, - const DcmTagKey &key, - DcmDirectoryRecord& record, - const char *defaultValue, - const OFBool printWarning) - { - OFCondition status; - if (dataset.tagExistsWithValue(key)) - { - OFString stringValue; - /* retrieve string value from source dataset and put it into the destination dataset */ - status = dataset.findAndGetOFStringArray(key, stringValue); - if (status.good()) - status = record.putAndInsertString(key, stringValue.c_str()); - } else { - if (printWarning && (defaultValue != NULL)) - { - /* create warning message */ - LOG(WARNING) << "DICOMDIR: " << DcmTag(key).getTagName() << " " - << key << " missing, using alternative: " << defaultValue; - } - /* put default value */ - status = record.putAndInsertString(key, defaultValue); - } - } - - // create alternative study date if absent in dataset - static OFString &alternativeStudyDate(DcmItem& dataset, - OFString &result) - { - /* use another date if present */ - if (dataset.findAndGetOFStringArray(DCM_SeriesDate, result).bad() || result.empty()) - { - if (dataset.findAndGetOFStringArray(DCM_AcquisitionDate, result).bad() || result.empty()) - { - if (dataset.findAndGetOFStringArray(DCM_ContentDate, result).bad() || result.empty()) - { - /* use current date, "19000101" in case of error */ - DcmDate::getCurrentDate(result); - } - } - } - return result; - } - - - // create alternative study time if absent in dataset - static OFString &alternativeStudyTime(DcmItem& dataset, - OFString &result) - { - /* use another time if present */ - if (dataset.findAndGetOFStringArray(DCM_SeriesTime, result).bad() || result.empty()) - { - if (dataset.findAndGetOFStringArray(DCM_AcquisitionTime, result).bad() || result.empty()) - { - if (dataset.findAndGetOFStringArray(DCM_ContentTime, result).bad() || result.empty()) - { - /* use current time, "0000" in case of error */ - DcmTime::getCurrentTime(result); - } - } - } - return result; - } - - - static void copyElementType1(DcmItem& dataset, - const DcmTagKey &key, - DcmDirectoryRecord& record) - { - copyElement(dataset, key, record, OFFalse /*optional*/, OFFalse /*copyEmpty*/); - } - - static void copyElementType1C(DcmItem& dataset, - const DcmTagKey &key, - DcmDirectoryRecord& record) - { - copyElement(dataset, key, record, OFTrue /*optional*/, OFFalse /*copyEmpty*/); - } - - static void copyElementType2(DcmItem& dataset, - const DcmTagKey &key, - DcmDirectoryRecord& record) - { - copyElement(dataset, key, record, OFFalse /*optional*/, OFTrue /*copyEmpty*/); - } - - /******************************************************************************* - * End of functions adapted from "dcmdata/libsrc/dcddirif.cc" from DCMTK 3.6.0 - *******************************************************************************/ - - - DcmDicomDir& GetDicomDir() - { - if (dir_.get() == NULL) - { - dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), - fileSetId_.c_str())); - } - - return *dir_; - } - - - DcmDirectoryRecord& GetRoot() - { - return GetDicomDir().getRootRecord(); - } - - - public: - PImpl() : fileSetId_("ORTHANC_MEDIA") - { - } - - void FillPatient(DcmDirectoryRecord& record, - DcmItem& dicom) - { - // cf. "DicomDirInterface::buildPatientRecord()" - - copyElementType1C(dicom, DCM_PatientID, record); - copyElementType2(dicom, DCM_PatientName, record); - } - - void FillStudy(DcmDirectoryRecord& record, - DcmItem& dicom) - { - // cf. "DicomDirInterface::buildStudyRecord()" - - OFString tmpString; - /* copy attribute values from dataset to study record */ - copyStringWithDefault(dicom, DCM_StudyDate, record, - alternativeStudyDate(dicom, tmpString).c_str(), OFTrue /*printWarning*/); - copyStringWithDefault(dicom, DCM_StudyTime, record, - alternativeStudyTime(dicom, tmpString).c_str(), OFTrue /*printWarning*/); - copyElementType2(dicom, DCM_StudyDescription, record); - copyElementType1(dicom, DCM_StudyInstanceUID, record); - /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - copyElementType1C(dicom, DCM_StudyID, record); - copyElementType2(dicom, DCM_AccessionNumber, record); - } - - void FillSeries(DcmDirectoryRecord& record, - DcmItem& dicom) - { - // cf. "DicomDirInterface::buildSeriesRecord()" - - /* copy attribute values from dataset to series record */ - copyElementType1(dicom, DCM_Modality, record); - copyElementType1(dicom, DCM_SeriesInstanceUID, record); - /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - copyElementType1C(dicom, DCM_SeriesNumber, record); - } - - void FillInstance(DcmDirectoryRecord& record, - DcmItem& dicom, - DcmMetaInfo& metaInfo, - const char* path) - { - // cf. "DicomDirInterface::buildImageRecord()" - - /* copy attribute values from dataset to image record */ - copyElementType1(dicom, DCM_InstanceNumber, record); - //copyElementType1C(dicom, DCM_ImageType, record); - copyElementType1C(dicom, DCM_ReferencedImageSequence, record); - - OFString tmp; - - DcmElement* item = record.remove(DCM_ReferencedImageSequence); - if (item != NULL) - { - delete item; - } - - if (record.putAndInsertString(DCM_ReferencedFileID, path).bad() || - dicom.findAndGetOFStringArray(DCM_SOPClassUID, tmp).bad() || - record.putAndInsertString(DCM_ReferencedSOPClassUIDInFile, tmp.c_str()).bad() || - dicom.findAndGetOFStringArray(DCM_SOPInstanceUID, tmp).bad() || - record.putAndInsertString(DCM_ReferencedSOPInstanceUIDInFile, tmp.c_str()).bad() || - metaInfo.findAndGetOFStringArray(DCM_TransferSyntaxUID, tmp).bad() || - record.putAndInsertString(DCM_ReferencedTransferSyntaxUIDInFile, tmp.c_str()).bad()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - - bool CreateResource(DcmDirectoryRecord*& target, - ResourceType level, - DcmFileFormat& dicom, - const char* filename, - const char* path) - { - DcmDataset& dataset = *dicom.getDataset(); - - OFCondition result; - OFString id; - E_DirRecType type; - - switch (level) - { - case ResourceType_Patient: - result = dataset.findAndGetOFString(DCM_PatientID, id); - type = ERT_Patient; - break; - - case ResourceType_Study: - result = dataset.findAndGetOFString(DCM_StudyInstanceUID, id); - type = ERT_Study; - break; - - case ResourceType_Series: - result = dataset.findAndGetOFString(DCM_SeriesInstanceUID, id); - type = ERT_Series; - break; - - case ResourceType_Instance: - result = dataset.findAndGetOFString(DCM_SOPInstanceUID, id); - type = ERT_Image; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (!result.good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - IndexKey key = std::make_pair(level, std::string(id.c_str())); - Index::iterator it = index_.find(key); - - if (it != index_.end()) - { - target = it->second; - return false; // Already existing - } - - std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); - - switch (level) - { - case ResourceType_Patient: - FillPatient(*record, dataset); - break; - - case ResourceType_Study: - FillStudy(*record, dataset); - break; - - case ResourceType_Series: - FillSeries(*record, dataset); - break; - - case ResourceType_Instance: - FillInstance(*record, dataset, *dicom.getMetaInfo(), path); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (record->isAffectedBySpecificCharacterSet()) - { - copyElementType1C(dataset, DCM_SpecificCharacterSet, *record); - } - - target = record.get(); - GetRoot().insertSub(record.release()); - index_[key] = target; - - return true; // Newly created - } - - void Read(std::string& s) - { - if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, - EET_UndefinedLength /*encodingType*/, - EGL_withoutGL /*groupLength*/).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - file_.Read(s); - } - - void SetFileSetId(const std::string& id) - { - dir_.reset(NULL); - fileSetId_ = id; - } - }; - - - DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl) - { - } - - DicomDirWriter::~DicomDirWriter() - { - if (pimpl_) - { - delete pimpl_; - } - } - - void DicomDirWriter::SetFileSetId(const std::string& id) - { - pimpl_->SetFileSetId(id); - } - - void DicomDirWriter::Add(const std::string& directory, - const std::string& filename, - ParsedDicomFile& dicom) - { - std::string path; - if (directory.empty()) - { - path = filename; - } - else - { - if (directory[directory.length() - 1] == '/' || - directory[directory.length() - 1] == '\\') - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - path = directory + '\\' + filename; - } - - DcmFileFormat& fileFormat = *reinterpret_cast<DcmFileFormat*>(dicom.GetDcmtkObject()); - - DcmDirectoryRecord* instance; - bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, fileFormat, filename.c_str(), path.c_str()); - if (isNewInstance) - { - DcmDirectoryRecord* series; - bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, fileFormat, filename.c_str(), NULL); - series->insertSub(instance); - - if (isNewSeries) - { - DcmDirectoryRecord* study; - bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, fileFormat, filename.c_str(), NULL); - study->insertSub(series); - - if (isNewStudy) - { - DcmDirectoryRecord* patient; - pimpl_->CreateResource(patient, ResourceType_Patient, fileFormat, filename.c_str(), NULL); - patient->insertSub(study); - } - } - } - } - - void DicomDirWriter::Encode(std::string& target) - { - pimpl_->Read(target); - } -}
--- a/OrthancServer/DicomDirWriter.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ParsedDicomFile.h" - -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class DicomDirWriter : public boost::noncopyable - { - private: - class PImpl; - PImpl* pimpl_; - - public: - DicomDirWriter(); - - ~DicomDirWriter(); - - void SetFileSetId(const std::string& id); - - void Add(const std::string& directory, - const std::string& filename, - ParsedDicomFile& dicom); - - void Encode(std::string& target); - }; - -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomInstanceOrigin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,211 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomInstanceOrigin.h" + +#include "../Core/OrthancException.h" +#include "../Core/SerializationToolbox.h" + + +namespace Orthanc +{ + void DicomInstanceOrigin::Format(Json::Value& result) const + { + result = Json::objectValue; + result["RequestOrigin"] = EnumerationToString(origin_); + + switch (origin_) + { + case RequestOrigin_Unknown: + { + // None of the methods "SetDicomProtocolOrigin()", "SetHttpOrigin()", + // "SetLuaOrigin()" or "SetPluginsOrigin()" was called! + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + case RequestOrigin_DicomProtocol: + { + result["RemoteIp"] = remoteIp_; + result["RemoteAet"] = dicomRemoteAet_; + result["CalledAet"] = dicomCalledAet_; + break; + } + + case RequestOrigin_RestApi: + { + result["RemoteIp"] = remoteIp_; + result["Username"] = httpUsername_; + break; + } + + case RequestOrigin_Lua: + case RequestOrigin_Plugins: + { + // No additional information available for these kinds of requests + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + DicomInstanceOrigin DicomInstanceOrigin::FromDicomProtocol(const char* remoteIp, + const char* remoteAet, + const char* calledAet) + { + DicomInstanceOrigin result(RequestOrigin_DicomProtocol); + result.remoteIp_ = remoteIp; + result.dicomRemoteAet_ = remoteAet; + result.dicomCalledAet_ = calledAet; + return result; + } + + DicomInstanceOrigin DicomInstanceOrigin::FromRest(const RestApiCall& call) + { + DicomInstanceOrigin result(call.GetRequestOrigin()); + + if (result.origin_ == RequestOrigin_RestApi) + { + result.remoteIp_ = call.GetRemoteIp(); + result.httpUsername_ = call.GetUsername(); + } + + return result; + } + + DicomInstanceOrigin DicomInstanceOrigin::FromHttp(const char* remoteIp, + const char* username) + { + DicomInstanceOrigin result(RequestOrigin_RestApi); + result.remoteIp_ = remoteIp; + result.httpUsername_ = username; + return result; + } + + const char* DicomInstanceOrigin::GetRemoteAetC() const + { + if (origin_ == RequestOrigin_DicomProtocol) + { + return dicomRemoteAet_.c_str(); + } + else + { + return ""; + } + } + + bool DicomInstanceOrigin::LookupRemoteAet(std::string& result) const + { + if (origin_ == RequestOrigin_DicomProtocol) + { + result = dicomRemoteAet_.c_str(); + return true; + } + else + { + return false; + } + } + + bool DicomInstanceOrigin::LookupRemoteIp(std::string& result) const + { + if (origin_ == RequestOrigin_DicomProtocol || + origin_ == RequestOrigin_RestApi) + { + result = remoteIp_; + return true; + } + else + { + return false; + } + } + + bool DicomInstanceOrigin::LookupCalledAet(std::string& result) const + { + if (origin_ == RequestOrigin_DicomProtocol) + { + result = dicomCalledAet_; + return true; + } + else + { + return false; + } + } + + bool DicomInstanceOrigin::LookupHttpUsername(std::string& result) const + { + if (origin_ == RequestOrigin_RestApi) + { + result = httpUsername_; + return true; + } + else + { + return false; + } + } + + + + static const char* ORIGIN = "Origin"; + static const char* REMOTE_IP = "RemoteIP"; + static const char* DICOM_REMOTE_AET = "RemoteAET"; + static const char* DICOM_CALLED_AET = "CalledAET"; + static const char* HTTP_USERNAME = "Username"; + + + DicomInstanceOrigin::DicomInstanceOrigin(const Json::Value& serialized) + { + origin_ = StringToRequestOrigin(SerializationToolbox::ReadString(serialized, ORIGIN)); + remoteIp_ = SerializationToolbox::ReadString(serialized, REMOTE_IP); + dicomRemoteAet_ = SerializationToolbox::ReadString(serialized, DICOM_REMOTE_AET); + dicomCalledAet_ = SerializationToolbox::ReadString(serialized, DICOM_CALLED_AET); + httpUsername_ = SerializationToolbox::ReadString(serialized, HTTP_USERNAME); + } + + + void DicomInstanceOrigin::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result[ORIGIN] = EnumerationToString(origin_); + result[REMOTE_IP] = remoteIp_; + result[DICOM_REMOTE_AET] = dicomRemoteAet_; + result[DICOM_CALLED_AET] = dicomCalledAet_; + result[HTTP_USERNAME] = httpUsername_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomInstanceOrigin.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,100 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/RestApi/RestApiCall.h" + +namespace Orthanc +{ + class DicomInstanceOrigin + { + private: + RequestOrigin origin_; + std::string remoteIp_; + std::string dicomRemoteAet_; + std::string dicomCalledAet_; + std::string httpUsername_; + + DicomInstanceOrigin(RequestOrigin origin) : + origin_(origin) + { + } + + public: + DicomInstanceOrigin() : + origin_(RequestOrigin_Unknown) + { + } + + DicomInstanceOrigin(const Json::Value& serialized); + + static DicomInstanceOrigin FromDicomProtocol(const char* remoteIp, + const char* remoteAet, + const char* calledAet); + + static DicomInstanceOrigin FromRest(const RestApiCall& call); + + static DicomInstanceOrigin FromHttp(const char* remoteIp, + const char* username); + + static DicomInstanceOrigin FromLua() + { + return DicomInstanceOrigin(RequestOrigin_Lua); + } + + static DicomInstanceOrigin FromPlugins() + { + return DicomInstanceOrigin(RequestOrigin_Plugins); + } + + RequestOrigin GetRequestOrigin() const + { + return origin_; + } + + const char* GetRemoteAetC() const; + + bool LookupRemoteAet(std::string& result) const; + + bool LookupRemoteIp(std::string& result) const; + + bool LookupCalledAet(std::string& result) const; + + bool LookupHttpUsername(std::string& result) const; + + void Format(Json::Value& result) const; + + void Serialize(Json::Value& result) const; + }; +}
--- a/OrthancServer/DicomInstanceToStore.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/DicomInstanceToStore.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,20 +34,15 @@ #include "PrecompiledHeadersServer.h" #include "DicomInstanceToStore.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/Logging.h" #include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcdeftag.h> namespace Orthanc { - static DcmDataset& GetDataset(ParsedDicomFile& file) - { - return *reinterpret_cast<DcmFileFormat*>(file.GetDcmtkObject())->getDataset(); - } - - void DicomInstanceToStore::AddMetadata(ResourceType level, MetadataType metadata, const std::string& value) @@ -69,17 +65,23 @@ { if (!parsed_.HasContent()) { - throw OrthancException(ErrorCode_NotImplemented); + if (!summary_.HasContent()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + else + { + parsed_.TakeOwnership(new ParsedDicomFile(summary_.GetConstContent())); + } } - else + + // Serialize the parsed DICOM file + buffer_.Allocate(); + if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), + *parsed_.GetContent().GetDcmtkObject().getDataset())) { - // Serialize the parsed DICOM file - buffer_.Allocate(); - if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), GetDataset(parsed_.GetContent()))) - { - LOG(ERROR) << "Unable to serialize a DICOM file to a memory buffer"; - throw OrthancException(ErrorCode_InternalError); - } + LOG(ERROR) << "Unable to serialize a DICOM file to a memory buffer"; + throw OrthancException(ErrorCode_InternalError); } } @@ -103,16 +105,18 @@ if (!summary_.HasContent()) { summary_.Allocate(); - FromDcmtkBridge::Convert(summary_.GetContent(), GetDataset(parsed_.GetContent())); + FromDcmtkBridge::ExtractDicomSummary(summary_.GetContent(), + *parsed_.GetContent().GetDcmtkObject().getDataset()); } if (!json_.HasContent()) { json_.Allocate(); - FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()), - DicomToJsonFormat_Full, - DicomToJsonFlags_Default, - 256 /* max string length */); + + std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ExtractDicomAsJson(json_.GetContent(), + *parsed_.GetContent().GetDcmtkObject().getDataset(), + ignoreTagLength); } } @@ -177,97 +181,23 @@ } - - void DicomInstanceToStore::GetOriginInformation(Json::Value& result) const + bool DicomInstanceToStore::LookupTransferSyntax(std::string& result) { - result = Json::objectValue; - result["RequestOrigin"] = EnumerationToString(origin_); - - switch (origin_) - { - case RequestOrigin_Unknown: - { - // None of the methods "SetDicomProtocolOrigin()", "SetHttpOrigin()", - // "SetLuaOrigin()" or "SetPluginsOrigin()" was called! - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - case RequestOrigin_DicomProtocol: - { - result["RemoteIp"] = remoteIp_; - result["RemoteAet"] = dicomRemoteAet_; - result["CalledAet"] = dicomCalledAet_; - break; - } - - case RequestOrigin_Http: - { - result["RemoteIp"] = remoteIp_; - result["Username"] = httpUsername_; - break; - } - - case RequestOrigin_Lua: - case RequestOrigin_Plugins: - { - // No additional information available for these kinds of requests - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - + ComputeMissingInformation(); - void DicomInstanceToStore::SetDicomProtocolOrigin(const char* remoteIp, - const char* remoteAet, - const char* calledAet) - { - origin_ = RequestOrigin_DicomProtocol; - remoteIp_ = remoteIp; - dicomRemoteAet_ = remoteAet; - dicomCalledAet_ = calledAet; - } - - void DicomInstanceToStore::SetRestOrigin(const RestApiCall& call) - { - origin_ = call.GetRequestOrigin(); - - if (origin_ == RequestOrigin_Http) + DicomMap header; + if (DicomMap::ParseDicomMetaInformation(header, GetBufferData(), GetBufferSize())) { - remoteIp_ = call.GetRemoteIp(); - httpUsername_ = call.GetUsername(); + const DicomValue* value = header.TestAndGetValue(DICOM_TAG_TRANSFER_SYNTAX_UID); + if (value != NULL && + !value->IsBinary() && + !value->IsNull()) + { + result = Toolbox::StripSpaces(value->GetContent()); + return true; + } } - } - void DicomInstanceToStore::SetHttpOrigin(const char* remoteIp, - const char* username) - { - origin_ = RequestOrigin_Http; - remoteIp_ = remoteIp; - httpUsername_ = username; - } - - void DicomInstanceToStore::SetLuaOrigin() - { - origin_ = RequestOrigin_Lua; - } - - void DicomInstanceToStore::SetPluginsOrigin() - { - origin_ = RequestOrigin_Plugins; - } - - const char* DicomInstanceToStore::GetRemoteAet() const - { - if (origin_ == RequestOrigin_DicomProtocol) - { - return dicomRemoteAet_.c_str(); - } - else - { - return ""; - } + return false; } }
--- a/OrthancServer/DicomInstanceToStore.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/DicomInstanceToStore.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,10 +33,10 @@ #pragma once -#include "ParsedDicomFile.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" +#include "../Core/OrthancException.h" +#include "DicomInstanceOrigin.h" #include "ServerIndex.h" -#include "../Core/OrthancException.h" -#include "../Core/RestApi/RestApiCall.h" namespace Orthanc { @@ -138,41 +139,26 @@ } }; - - SmartContainer<std::string> buffer_; + DicomInstanceOrigin origin_; + SmartContainer<std::string> buffer_; SmartContainer<ParsedDicomFile> parsed_; - SmartContainer<DicomMap> summary_; - SmartContainer<Json::Value> json_; - - RequestOrigin origin_; - std::string remoteIp_; - std::string dicomRemoteAet_; - std::string dicomCalledAet_; - std::string httpUsername_; - ServerIndex::MetadataMap metadata_; + SmartContainer<DicomMap> summary_; + SmartContainer<Json::Value> json_; + ServerIndex::MetadataMap metadata_; void ComputeMissingInformation(); public: - DicomInstanceToStore() : origin_(RequestOrigin_Unknown) + void SetOrigin(const DicomInstanceOrigin& origin) { + origin_ = origin; } - - void SetDicomProtocolOrigin(const char* remoteIp, - const char* remoteAet, - const char* calledAet); - - void SetRestOrigin(const RestApiCall& call); - - void SetHttpOrigin(const char* remoteIp, - const char* username); - - void SetLuaOrigin(); - - void SetPluginsOrigin(); - - const char* GetRemoteAet() const; - + + const DicomInstanceOrigin& GetOrigin() const + { + return origin_; + } + void SetBuffer(const std::string& dicom) { buffer_.SetConstReference(dicom); @@ -215,6 +201,6 @@ const Json::Value& GetJson(); - void GetOriginInformation(Json::Value& result) const; + bool LookupTransferSyntax(std::string& result); }; }
--- a/OrthancServer/DicomModification.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,484 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersServer.h" -#include "DicomModification.h" - -#include "../Core/Logging.h" -#include "../Core/OrthancException.h" -#include "FromDcmtkBridge.h" - -#include <memory> // For std::auto_ptr - - -static const std::string ORTHANC_DEIDENTIFICATION_METHOD = "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; - -namespace Orthanc -{ - void DicomModification::RemoveInternal(const DicomTag& tag) - { - Replacements::iterator it = replacements_.find(tag); - - if (it != replacements_.end()) - { - delete it->second; - replacements_.erase(it); - } - } - - - void DicomModification::ReplaceInternal(const DicomTag& tag, - const Json::Value& value) - { - Replacements::iterator it = replacements_.find(tag); - - if (it != replacements_.end()) - { - delete it->second; - it->second = NULL; // In the case of an exception during the clone - it->second = new Json::Value(value); // Clone - } - else - { - replacements_[tag] = new Json::Value(value); // Clone - } - } - - - void DicomModification::ClearReplacements() - { - for (Replacements::iterator it = replacements_.begin(); - it != replacements_.end(); ++it) - { - delete it->second; - } - - replacements_.clear(); - } - - - void DicomModification::MarkNotOrthancAnonymization() - { - Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); - - if (it != replacements_.end() && - it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD) - { - delete it->second; - replacements_.erase(it); - } - } - - - void DicomModification::MapDicomIdentifier(ParsedDicomFile& dicom, - ResourceType level) - { - std::auto_ptr<DicomTag> tag; - - switch (level) - { - case ResourceType_Study: - tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - break; - - case ResourceType_Series: - tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - break; - - case ResourceType_Instance: - tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID)); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - std::string original; - if (!dicom.GetTagValue(original, *tag)) - { - original = ""; - } - - std::string mapped; - - UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); - if (previous == uidMap_.end()) - { - mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); - uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); - } - else - { - mapped = previous->second; - } - - dicom.Replace(*tag, mapped); - } - - DicomModification::DicomModification() - { - removePrivateTags_ = false; - level_ = ResourceType_Instance; - allowManualIdentifiers_ = true; - } - - DicomModification::~DicomModification() - { - ClearReplacements(); - } - - void DicomModification::Keep(const DicomTag& tag) - { - removals_.erase(tag); - RemoveInternal(tag); - - if (FromDcmtkBridge::IsPrivateTag(tag)) - { - privateTagsToKeep_.insert(tag); - } - - MarkNotOrthancAnonymization(); - } - - void DicomModification::Remove(const DicomTag& tag) - { - removals_.insert(tag); - RemoveInternal(tag); - privateTagsToKeep_.erase(tag); - - MarkNotOrthancAnonymization(); - } - - bool DicomModification::IsRemoved(const DicomTag& tag) const - { - return removals_.find(tag) != removals_.end(); - } - - void DicomModification::Replace(const DicomTag& tag, - const Json::Value& value, - bool safeForAnonymization) - { - removals_.erase(tag); - privateTagsToKeep_.erase(tag); - ReplaceInternal(tag, value); - - if (!safeForAnonymization) - { - MarkNotOrthancAnonymization(); - } - } - - - bool DicomModification::IsReplaced(const DicomTag& tag) const - { - return replacements_.find(tag) != replacements_.end(); - } - - const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const - { - Replacements::const_iterator it = replacements_.find(tag); - - if (it == replacements_.end()) - { - throw OrthancException(ErrorCode_InexistentItem); - } - else - { - return *it->second; - } - } - - - std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const - { - const Json::Value& json = GetReplacement(tag); - - if (json.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - else - { - return json.asString(); - } - } - - - void DicomModification::SetRemovePrivateTags(bool removed) - { - removePrivateTags_ = removed; - - if (!removed) - { - MarkNotOrthancAnonymization(); - } - } - - void DicomModification::SetLevel(ResourceType level) - { - uidMap_.clear(); - level_ = level; - - if (level != ResourceType_Patient) - { - MarkNotOrthancAnonymization(); - } - } - - void DicomModification::SetupAnonymization() - { - removals_.clear(); - ClearReplacements(); - removePrivateTags_ = true; - level_ = ResourceType_Patient; - uidMap_.clear(); - privateTagsToKeep_.clear(); - - // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles - removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID - //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() - removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number - removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name - removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name - removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name - removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID - removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) - //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) - removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids - removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number - removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() - //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() - removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID - removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals_.insert(DicomTag(0x0040, 0xa124)); // UID - removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID - removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID - removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID - - // Some more removals (from the experience of DICOM files at the CHU of Liege) - removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address - removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers - removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - - // Set the DeidentificationMethod tag - ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD); - - // Set the PatientIdentityRemoved tag - ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); - - // (*) Choose a random patient name and ID - std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); - ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); - ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); - } - - void DicomModification::Apply(ParsedDicomFile& toModify) - { - // Check the request - assert(ResourceType_Patient + 1 == ResourceType_Study && - ResourceType_Study + 1 == ResourceType_Series && - ResourceType_Series + 1 == ResourceType_Instance); - - if (IsRemoved(DICOM_TAG_PATIENT_ID) || - IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || - IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || - IsRemoved(DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest); - } - - - // Sanity checks at the patient level - if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - } - - - // Sanity checks at the study level - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying a study, the parent PatientID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - } - - - // Sanity checks at the series level - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying a series, the parent PatientID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a series, the parent StudyInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - } - - - // Sanity checks at the instance level - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying an instance, the parent PatientID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying an instance, the parent StudyInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - - // (1) Remove the private tags, if need be - if (removePrivateTags_) - { - toModify.RemovePrivateTags(privateTagsToKeep_); - } - - // (2) Remove the tags specified by the user - for (SetOfTags::const_iterator it = removals_.begin(); - it != removals_.end(); ++it) - { - toModify.Remove(*it); - } - - // (3) Replace the tags - for (Replacements::const_iterator it = replacements_.begin(); - it != replacements_.end(); ++it) - { - toModify.Replace(it->first, *it->second, DicomReplaceMode_InsertIfAbsent); - } - - // (4) Update the DICOM identifiers - if (level_ <= ResourceType_Study && - !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - MapDicomIdentifier(toModify, ResourceType_Study); - } - - if (level_ <= ResourceType_Series && - !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - MapDicomIdentifier(toModify, ResourceType_Series); - } - - if (level_ <= ResourceType_Instance && // Always true - !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - MapDicomIdentifier(toModify, ResourceType_Instance); - } - } -}
--- a/OrthancServer/DicomModification.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ParsedDicomFile.h" - -namespace Orthanc -{ - class DicomModification : public boost::noncopyable - { - /** - * Process: - * (1) Remove private tags - * (2) Remove tags specified by the user - * (3) Replace tags - **/ - - private: - typedef std::set<DicomTag> SetOfTags; - typedef std::map<DicomTag, Json::Value*> Replacements; - typedef std::map< std::pair<ResourceType, std::string>, std::string> UidMap; - - SetOfTags removals_; - Replacements replacements_; - bool removePrivateTags_; - ResourceType level_; - UidMap uidMap_; - SetOfTags privateTagsToKeep_; - bool allowManualIdentifiers_; - - void MapDicomIdentifier(ParsedDicomFile& dicom, - ResourceType level); - - void MarkNotOrthancAnonymization(); - - void ClearReplacements(); - - void RemoveInternal(const DicomTag& tag); - - void ReplaceInternal(const DicomTag& tag, - const Json::Value& value); - - public: - DicomModification(); - - ~DicomModification(); - - void Keep(const DicomTag& tag); - - void Remove(const DicomTag& tag); - - bool IsRemoved(const DicomTag& tag) const; - - void Replace(const DicomTag& tag, - const Json::Value& value, // Encoded using UTF-8 - bool safeForAnonymization = false); - - bool IsReplaced(const DicomTag& tag) const; - - const Json::Value& GetReplacement(const DicomTag& tag) const; - - std::string GetReplacementAsString(const DicomTag& tag) const; - - void SetRemovePrivateTags(bool removed); - - bool ArePrivateTagsRemoved() const - { - return removePrivateTags_; - } - - void SetLevel(ResourceType level); - - ResourceType GetLevel() const - { - return level_; - } - - void SetupAnonymization(); - - void Apply(ParsedDicomFile& toModify); - - void SetAllowManualIdentifiers(bool check) - { - allowManualIdentifiers_ = check; - } - - bool AreAllowManualIdentifiers() const - { - return allowManualIdentifiers_; - } - }; -}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomFindAnswers.h" - -#include "../FromDcmtkBridge.h" - -namespace Orthanc -{ - void DicomFindAnswers::Clear() - { - for (size_t i = 0; i < items_.size(); i++) - { - delete items_[i]; - } - } - - void DicomFindAnswers::Reserve(size_t size) - { - if (size > items_.size()) - { - items_.reserve(size); - } - } - - void DicomFindAnswers::ToJson(Json::Value& target, - bool simplify) const - { - target = Json::arrayValue; - - for (size_t i = 0; i < GetSize(); i++) - { - Json::Value answer(Json::objectValue); - FromDcmtkBridge::ToJson(answer, GetAnswer(i), simplify); - target.append(answer); - } - } -}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/DicomFormat/DicomMap.h" - -#include <vector> -#include <json/json.h> - -namespace Orthanc -{ - class DicomFindAnswers - { - private: - std::vector<DicomMap*> items_; - - public: - ~DicomFindAnswers() - { - Clear(); - } - - void Clear(); - - void Reserve(size_t index); - - void Add(const DicomMap& map) - { - items_.push_back(map.Clone()); - } - - size_t GetSize() const - { - return items_.size(); - } - - const DicomMap& GetAnswer(size_t index) const - { - return *items_.at(index); - } - - void ToJson(Json::Value& target, - bool simplify) const; - }; -}
--- a/OrthancServer/DicomProtocol/DicomServer.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,325 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomServer.h" - -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" -#include "../../Core/Toolbox.h" -#include "../../Core/Uuid.h" -#include "../Internals/CommandDispatcher.h" -#include "../OrthancInitialization.h" -#include "EmbeddedResources.h" -#include "../../Core/MultiThreading/RunnableWorkersPool.h" - -#include <boost/thread.hpp> - -#if defined(__linux) -#include <cstdlib> -#endif - - -namespace Orthanc -{ - struct DicomServer::PImpl - { - boost::thread thread_; - T_ASC_Network *network_; - std::auto_ptr<RunnableWorkersPool> workers_; - }; - - - void DicomServer::ServerThread(DicomServer* server) - { - LOG(INFO) << "DICOM server started"; - - while (server->continue_) - { - /* 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_)); - - try - { - if (dispatcher.get() != NULL) - { - server->pimpl_->workers_->Add(dispatcher.release()); - } - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception in the DICOM server thread: " << e.What(); - } - } - - LOG(INFO) << "DICOM server stopping"; - } - - - DicomServer::DicomServer() : - pimpl_(new PImpl), - aet_("ANY-SCP") - { - port_ = 104; - findRequestHandlerFactory_ = NULL; - moveRequestHandlerFactory_ = NULL; - storeRequestHandlerFactory_ = NULL; - applicationEntityFilter_ = NULL; - checkCalledAet_ = true; - clientTimeout_ = 30; - continue_ = false; - } - - DicomServer::~DicomServer() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - void DicomServer::SetPortNumber(uint16_t port) - { - Stop(); - port_ = port; - } - - uint16_t DicomServer::GetPortNumber() const - { - return port_; - } - - void DicomServer::SetClientTimeout(uint32_t timeout) - { - Stop(); - clientTimeout_ = timeout; - } - - uint32_t DicomServer::GetClientTimeout() const - { - return clientTimeout_; - } - - - void DicomServer::SetCalledApplicationEntityTitleCheck(bool check) - { - Stop(); - checkCalledAet_ = check; - } - - bool DicomServer::HasCalledApplicationEntityTitleCheck() const - { - return checkCalledAet_; - } - - void DicomServer::SetApplicationEntityTitle(const std::string& aet) - { - if (aet.size() == 0) - { - throw OrthancException(ErrorCode_BadApplicationEntityTitle); - } - - if (aet.size() > 16) - { - throw OrthancException(ErrorCode_BadApplicationEntityTitle); - } - - for (size_t i = 0; i < aet.size(); i++) - { - if (!(aet[i] == '-' || - aet[i] == '_' || - isdigit(aet[i]) || - (aet[i] >= 'A' && aet[i] <= 'Z'))) - { - LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\""; - break; - } - } - - Stop(); - aet_ = aet; - } - - const std::string& DicomServer::GetApplicationEntityTitle() const - { - return aet_; - } - - void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory) - { - Stop(); - findRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasFindRequestHandlerFactory() const - { - return (findRequestHandlerFactory_ != NULL); - } - - IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const - { - if (HasFindRequestHandlerFactory()) - { - return *findRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCFindHandler); - } - } - - void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory) - { - Stop(); - moveRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasMoveRequestHandlerFactory() const - { - return (moveRequestHandlerFactory_ != NULL); - } - - IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const - { - if (HasMoveRequestHandlerFactory()) - { - return *moveRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCMoveHandler); - } - } - - void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory) - { - Stop(); - storeRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasStoreRequestHandlerFactory() const - { - return (storeRequestHandlerFactory_ != NULL); - } - - IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const - { - if (HasStoreRequestHandlerFactory()) - { - return *storeRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCStoreHandler); - } - } - - void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory) - { - Stop(); - applicationEntityFilter_ = &factory; - } - - bool DicomServer::HasApplicationEntityFilter() const - { - return (applicationEntityFilter_ != NULL); - } - - IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const - { - if (HasApplicationEntityFilter()) - { - return *applicationEntityFilter_; - } - else - { - throw OrthancException(ErrorCode_NoApplicationEntityFilter); - } - } - - void DicomServer::Start() - { - Stop(); - - /* initialize network, i.e. create an instance of T_ASC_Network*. */ - OFCondition cond = ASC_initializeNetwork - (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_); - if (cond.bad()) - { - LOG(ERROR) << "cannot create network: " << cond.text(); - throw OrthancException(ErrorCode_DicomPortInUse); - } - - continue_ = true; - pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? - pimpl_->thread_ = boost::thread(ServerThread, this); - } - - - void DicomServer::Stop() - { - if (continue_) - { - continue_ = false; - - if (pimpl_->thread_.joinable()) - { - pimpl_->thread_.join(); - } - - pimpl_->workers_.reset(NULL); - - /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ - /* is the counterpart of ASC_initializeNetwork(...) which was called above. */ - OFCondition cond = ASC_dropNetwork(&pimpl_->network_); - if (cond.bad()) - { - LOG(ERROR) << "Error while dropping the network: " << cond.text(); - } - } - } - - - bool DicomServer::IsMyAETitle(const std::string& aet) const - { - if (!HasCalledApplicationEntityTitleCheck()) - { - // OK, no check on the AET. - return true; - } - - return Configuration::IsSameAETitle(aet, GetApplicationEntityTitle()); - } - -}
--- a/OrthancServer/DicomProtocol/DicomServer.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IFindRequestHandlerFactory.h" -#include "IMoveRequestHandlerFactory.h" -#include "IStoreRequestHandlerFactory.h" -#include "IApplicationEntityFilter.h" - -#include <boost/shared_ptr.hpp> -#include <boost/noncopyable.hpp> - - -namespace Orthanc -{ - class DicomServer : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - bool checkCalledAet_; - std::string aet_; - uint16_t port_; - bool continue_; - bool started_; - uint32_t clientTimeout_; - IFindRequestHandlerFactory* findRequestHandlerFactory_; - IMoveRequestHandlerFactory* moveRequestHandlerFactory_; - IStoreRequestHandlerFactory* storeRequestHandlerFactory_; - IApplicationEntityFilter* applicationEntityFilter_; - - static void ServerThread(DicomServer* server); - - public: - DicomServer(); - - ~DicomServer(); - - void SetPortNumber(uint16_t port); - uint16_t GetPortNumber() const; - - void SetClientTimeout(uint32_t timeout); - uint32_t GetClientTimeout() const; - - void SetCalledApplicationEntityTitleCheck(bool check); - bool HasCalledApplicationEntityTitleCheck() const; - - void SetApplicationEntityTitle(const std::string& aet); - const std::string& GetApplicationEntityTitle() const; - - void SetFindRequestHandlerFactory(IFindRequestHandlerFactory& handler); - bool HasFindRequestHandlerFactory() const; - IFindRequestHandlerFactory& GetFindRequestHandlerFactory() const; - - void SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& handler); - bool HasMoveRequestHandlerFactory() const; - IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const; - - void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler); - bool HasStoreRequestHandlerFactory() const; - IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const; - - void SetApplicationEntityFilter(IApplicationEntityFilter& handler); - bool HasApplicationEntityFilter() const; - IApplicationEntityFilter& GetApplicationEntityFilter() const; - - void Start(); - - void Stop(); - - bool IsMyAETitle(const std::string& aet) const; - }; - -}
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1104 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomUserConnection.h" - -#include "../../Core/DicomFormat/DicomArray.h" -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" - -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcistrmf.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmnet/diutil.h> - -#include <set> - - -#ifdef _WIN32 -/** - * "The maximum length, in bytes, of the string returned in the buffer - * pointed to by the name parameter is dependent on the namespace provider, - * but this string must be 256 bytes or less. - * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738527(v=vs.85).aspx - **/ -#define HOST_NAME_MAX 256 -#endif - - -#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) -/** - * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that - * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an - * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect - * that the result will fit." - * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html - **/ -#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX -#endif - - -static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; - -/** - * "If we have more than 64 storage SOP classes, tools such as - * storescu will fail because they attempt to negotiate two - * presentation contexts for each SOP class, and there is a total - * limit of 128 contexts for one association." - **/ -static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64; - - -namespace Orthanc -{ - struct DicomUserConnection::PImpl - { - // Connection state - uint32_t dimseTimeout_; - uint32_t acseTimeout_; - T_ASC_Network* net_; - T_ASC_Parameters* params_; - T_ASC_Association* assoc_; - - bool IsOpen() const - { - return assoc_ != NULL; - } - - void CheckIsOpen() const; - - void Store(DcmInputStream& is, DicomUserConnection& connection); - }; - - - static void Check(const OFCondition& cond) - { - if (cond.bad()) - { - LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text()); - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - void DicomUserConnection::PImpl::CheckIsOpen() const - { - if (!IsOpen()) - { - LOG(ERROR) << "DicomUserConnection: First open the connection"; - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - - void DicomUserConnection::CheckIsOpen() const - { - pimpl_->CheckIsOpen(); - } - - - static void RegisterStorageSOPClass(T_ASC_Parameters* params, - unsigned int& presentationContextId, - const std::string& sopClass, - const char* asPreferred[], - std::vector<const char*>& asFallback) - { - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), asPreferred, 1)); - presentationContextId += 2; - - if (asFallback.size() > 0) - { - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), &asFallback[0], asFallback.size())); - presentationContextId += 2; - } - } - - - void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) - { - // Flatten an array with the preferred transfer syntax - const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; - - // Setup the fallback transfer syntaxes - std::set<std::string> fallbackSyntaxes; - fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); - fallbackSyntaxes.erase(preferredTransferSyntax); - - // Flatten an array with the fallback transfer syntaxes - std::vector<const char*> asFallback; - asFallback.reserve(fallbackSyntaxes.size()); - for (std::set<std::string>::const_iterator - it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) - { - asFallback.push_back(it->c_str()); - } - - CheckStorageSOPClassesInvariant(); - 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); - } - - for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); - it != storageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback); - } - - for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); - it != defaultStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback); - } - } - - - static bool IsGenericTransferSyntax(const std::string& syntax) - { - return (syntax == UID_LittleEndianExplicitTransferSyntax || - syntax == UID_BigEndianExplicitTransferSyntax || - syntax == UID_LittleEndianImplicitTransferSyntax); - } - - - void DicomUserConnection::PImpl::Store(DcmInputStream& is, DicomUserConnection& connection) - { - CheckIsOpen(); - - DcmFileFormat dcmff; - Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); - - // Determine the storage SOP class UID for this instance - static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016); - OFString sopClassUid; - if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good()) - { - connection.AddStorageSOPClass(sopClassUid.c_str()); - } - - // Determine whether a new presentation context must be - // negotiated, depending on the transfer syntax of this instance - DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); - const std::string syntax(xfer.getXferID()); - bool isGeneric = IsGenericTransferSyntax(syntax); - - bool renegociate; - if (isGeneric) - { - // Are we making a generic-to-specific or specific-to-generic change of - // the transfer syntax? If this is the case, renegotiate the connection. - renegociate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); - } - else - { - // We are using a specific transfer syntax. Renegociate if the - // current connection does not match this transfer syntax. - renegociate = (syntax != connection.GetPreferredTransferSyntax()); - } - - if (renegociate) - { - LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; - - if (isGeneric) - { - connection.ResetPreferredTransferSyntax(); - } - else - { - connection.SetPreferredTransferSyntax(syntax); - } - } - - if (!connection.IsOpen()) - { - LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; - connection.Open(); - } - - // Figure out which SOP class and SOP instance is encapsulated in the file - DIC_UI sopClass; - DIC_UI sopInstance; - if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance)) - { - throw OrthancException(ErrorCode_NoSopClassOrInstance); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); - if (presID == 0) - { - const char *modalityName = dcmSOPClassUIDToModality(sopClass); - if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); - if (!modalityName) modalityName = "unknown SOP class"; - throw OrthancException(ErrorCode_NoPresentationContext); - } - - // Prepare the transmission of data - T_DIMSE_C_StoreRQ req; - memset(&req, 0, sizeof(req)); - req.MessageID = assoc_->nextMsgID++; - strcpy(req.AffectedSOPClassUID, sopClass); - strcpy(req.AffectedSOPInstanceUID, sopInstance); - req.DataSetType = DIMSE_DATASET_PRESENT; - req.Priority = DIMSE_PRIORITY_MEDIUM; - - // Finally conduct transmission of data - T_DIMSE_C_StoreRSP rsp; - DcmDataset* statusDetail = NULL; - Check(DIMSE_storeUser(assoc_, presID, &req, - NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL, - /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_, - &rsp, &statusDetail, NULL)); - - if (statusDetail != NULL) - { - delete statusDetail; - } - } - - - namespace - { - struct FindPayload - { - DicomFindAnswers* answers; - std::string level; - }; - } - - - static void FindCallback( - /* in */ - void *callbackData, - T_DIMSE_C_FindRQ *request, /* original find request */ - int responseCount, - T_DIMSE_C_FindRSP *response, /* pending response received */ - DcmDataset *responseIdentifiers /* pending response identifiers */ - ) - { - FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData); - - if (responseIdentifiers != NULL) - { - DicomMap m; - FromDcmtkBridge::Convert(m, *responseIdentifiers); - - if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level); - } - - payload.answers->Add(m); - } - } - - - static void FixFindQuery(DicomMap& fixedQuery, - ResourceType level, - const DicomMap& fields) - { - std::set<DicomTag> allowedTags; - - // WARNING: Do not add "break" or reorder items in this switch-case! - switch (level) - { - case ResourceType_Instance: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); - - case ResourceType_Series: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); - - case ResourceType_Study: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); - - case ResourceType_Patient: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (level == ResourceType_Study) - { - allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); - } - - allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); - - DicomArray query(fields); - for (size_t i = 0; i < query.GetSize(); i++) - { - const DicomTag& tag = query.GetElement(i).GetTag(); - if (allowedTags.find(tag) == allowedTags.end()) - { - LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; - } - else - { - fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); - } - } - } - - - static DcmDataset* ConvertQueryFields(const DicomMap& fields, - ModalityManufacturer manufacturer) - { - switch (manufacturer) - { - case ModalityManufacturer_SyngoVia: - { - std::auto_ptr<DicomMap> fix(fields.Clone()); - - // This issue for Syngo.Via and its solution was reported by - // Emsy Chan by private mail on June 17th, 2015. - std::set<DicomTag> tags; - fix->GetTags(tags); - - for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) - { - if (FromDcmtkBridge::GetValueRepresentation(*it) == ValueRepresentation_Date) - { - // Replace a "*" query by an empty query ("") for "date" - // value representations. Necessary to search over dates - // in Syngo.Via. - const DicomValue* value = fix->TestAndGetValue(*it); - - if (value != NULL && - !value->IsNull() && - value->GetContent() == "*") - { - fix->SetValue(*it, ""); - } - } - } - - return ToDcmtkBridge::Convert(*fix); - } - - default: - return ToDcmtkBridge::Convert(fields); - } - } - - - void DicomUserConnection::Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& originalFields) - { - DicomMap fields; - FixFindQuery(fields, level, originalFields); - - CheckIsOpen(); - - FindPayload payload; - payload.answers = &result; - - std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_)); - - const char* sopClass; - switch (level) - { - case ResourceType_Patient: - payload.level = "PATIENT"; - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); - sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - break; - - case ResourceType_Study: - payload.level = "STUDY"; - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Series: - payload.level = "SERIES"; - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Instance: - payload.level = "INSTANCE"; - if (manufacturer_ == ModalityManufacturer_ClearCanvas || - manufacturer_ == ModalityManufacturer_Dcm4Chee) - { - // 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.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); - } - else - { - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); - } - - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Add the expected tags for this query level. - // WARNING: Do not reorder or add "break" in this switch-case! - switch (level) - { - case ResourceType_Instance: - // SOP Instance UID - if (!fields.HasTag(0x0008, 0x0018)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); - - case ResourceType_Series: - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); - - case ResourceType_Study: - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - - // Study instance UID - if (!fields.HasTag(0x0020, 0x000d)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - - case ResourceType_Patient: - // Patient ID - if (!fields.HasTag(0x0010, 0x0020)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); - - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomFindUnavailable); - } - - T_DIMSE_C_FindRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = pimpl_->assoc_->nextMsgID++; - strcpy(request.AffectedSOPClassUID, sopClass); - request.DataSetType = DIMSE_DATASET_PRESENT; - request.Priority = DIMSE_PRIORITY_MEDIUM; - - T_DIMSE_C_FindRSP response; - DcmDataset* statusDetail = NULL; - OFCondition cond = DIMSE_findUser(pimpl_->assoc_, presID, &request, dataset.get(), - FindCallback, &payload, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - &response, &statusDetail); - - if (statusDetail) - { - delete statusDetail; - } - - Check(cond); - } - - - void DicomUserConnection::MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields) - { - CheckIsOpen(); - - std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_)); - - const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; - switch (level) - { - case ResourceType_Patient: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); - break; - - case ResourceType_Study: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); - break; - - case ResourceType_Series: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); - break; - - case ResourceType_Instance: - if (manufacturer_ == ModalityManufacturer_ClearCanvas || - manufacturer_ == ModalityManufacturer_Dcm4Chee) - { - // 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.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); - } - else - { - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); - } - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomMoveUnavailable); - } - - T_DIMSE_C_MoveRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = pimpl_->assoc_->nextMsgID++; - strcpy(request.AffectedSOPClassUID, sopClass); - request.DataSetType = DIMSE_DATASET_PRESENT; - request.Priority = DIMSE_PRIORITY_MEDIUM; - strncpy(request.MoveDestination, targetAet.c_str(), sizeof(DIC_AE) / sizeof(char)); - - T_DIMSE_C_MoveRSP response; - DcmDataset* statusDetail = NULL; - DcmDataset* responseIdentifiers = NULL; - OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset.get(), - NULL, NULL, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - pimpl_->net_, NULL, NULL, - &response, &statusDetail, &responseIdentifiers); - - if (statusDetail) - { - delete statusDetail; - } - - if (responseIdentifiers) - { - delete responseIdentifiers; - } - - Check(cond); - } - - - void DicomUserConnection::ResetStorageSOPClasses() - { - CheckStorageSOPClassesInvariant(); - - storageSOPClasses_.clear(); - defaultStorageSOPClasses_.clear(); - - // Copy the short list of storage SOP classes from DCMTK, making - // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE. - - std::set<std::string> uncommon; - uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); - uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); - uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); - uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); - - // Add the storage syntaxes for C-STORE - for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) - { - if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end()) - { - defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]); - } - } - - CheckStorageSOPClassesInvariant(); - } - - - DicomUserConnection::DicomUserConnection() : - pimpl_(new PImpl), - preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), - localAet_("STORESCU"), - remoteAet_("ANY-SCP"), - remoteHost_("127.0.0.1") - { - remotePort_ = 104; - manufacturer_ = ModalityManufacturer_Generic; - - SetTimeout(10); - pimpl_->net_ = NULL; - pimpl_->params_ = NULL; - pimpl_->assoc_ = NULL; - - // SOP classes for C-ECHO, C-FIND and C-MOVE - reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); - reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - - ResetStorageSOPClasses(); - } - - DicomUserConnection::~DicomUserConnection() - { - Close(); - } - - - void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters) - { - SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); - SetRemoteHost(parameters.GetHost()); - SetRemotePort(parameters.GetPort()); - SetRemoteManufacturer(parameters.GetManufacturer()); - } - - - void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) - { - if (localAet_ != aet) - { - Close(); - localAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet) - { - if (remoteAet_ != aet) - { - Close(); - remoteAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer) - { - if (manufacturer_ != manufacturer) - { - Close(); - manufacturer_ = manufacturer; - } - } - - void DicomUserConnection::ResetPreferredTransferSyntax() - { - SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX); - } - - void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax) - { - if (preferredTransferSyntax_ != preferredTransferSyntax) - { - Close(); - preferredTransferSyntax_ = preferredTransferSyntax; - } - } - - - void DicomUserConnection::SetRemoteHost(const std::string& host) - { - if (remoteHost_ != host) - { - if (host.size() > HOST_NAME_MAX - 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - Close(); - remoteHost_ = host; - } - } - - void DicomUserConnection::SetRemotePort(uint16_t port) - { - if (remotePort_ != port) - { - Close(); - remotePort_ = port; - } - } - - void DicomUserConnection::Open() - { - if (IsOpen()) - { - // Don't reopen the connection - return; - } - - LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() - << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host " - << GetRemoteHost() << ":" << GetRemotePort() - << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")"; - - Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_)); - Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); - - // Set this application's title and the called application's title in the params - Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL)); - - // Set the network addresses of the local and remote entities - char localHost[HOST_NAME_MAX]; - gethostname(localHost, HOST_NAME_MAX - 1); - - char remoteHostAndPort[HOST_NAME_MAX]; - -#ifdef _MSC_VER - _snprintf -#else - snprintf -#endif - (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_); - - Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort)); - - // Set various options - Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); - - SetupPresentationContexts(preferredTransferSyntax_); - - // Do the association - Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_)); - - if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0) - { - throw OrthancException(ErrorCode_NoPresentationContext); - } - } - - void DicomUserConnection::Close() - { - if (pimpl_->assoc_ != NULL) - { - ASC_releaseAssociation(pimpl_->assoc_); - ASC_destroyAssociation(&pimpl_->assoc_); - pimpl_->assoc_ = NULL; - pimpl_->params_ = NULL; - } - else - { - if (pimpl_->params_ != NULL) - { - ASC_destroyAssociationParameters(&pimpl_->params_); - pimpl_->params_ = NULL; - } - } - - if (pimpl_->net_ != NULL) - { - ASC_dropNetwork(&pimpl_->net_); - pimpl_->net_ = NULL; - } - } - - bool DicomUserConnection::IsOpen() const - { - return pimpl_->IsOpen(); - } - - void DicomUserConnection::Store(const char* buffer, size_t size) - { - // Prepare an input stream for the memory buffer - DcmInputBufferStream is; - if (size > 0) - is.setBuffer(buffer, size); - is.setEos(); - - pimpl_->Store(is, *this); - } - - void DicomUserConnection::Store(const std::string& buffer) - { - if (buffer.size() > 0) - Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size()); - else - Store(NULL, 0); - } - - void DicomUserConnection::StoreFile(const std::string& path) - { - // Prepare an input stream for the file - DcmInputFileStream is(path.c_str()); - pimpl_->Store(is, *this); - } - - bool DicomUserConnection::Echo() - { - CheckIsOpen(); - DIC_US status; - Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - &status, NULL)); - return status == STATUS_Success; - } - - - static void TestAndCopyTag(DicomMap& result, - const DicomMap& source, - const DicomTag& tag) - { - if (!source.HasTag(tag)) - { - throw OrthancException(ErrorCode_BadRequest); - } - else - { - result.SetValue(tag, source.GetValue(tag)); - } - } - - - void DicomUserConnection::Move(const std::string& targetAet, - const DicomMap& findResult) - { - if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - throw OrthancException(ErrorCode_InternalError); - } - - const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); - ResourceType level = StringToResourceType(tmp.c_str()); - - DicomMap move; - switch (level) - { - case ResourceType_Patient: - TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); - break; - - case ResourceType_Study: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - break; - - case ResourceType_Series: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - break; - - case ResourceType_Instance: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - MoveInternal(targetAet, level, move); - } - - - void DicomUserConnection::MovePatient(const std::string& targetAet, - const std::string& patientId) - { - DicomMap query; - query.SetValue(DICOM_TAG_PATIENT_ID, patientId); - MoveInternal(targetAet, ResourceType_Patient, query); - } - - void DicomUserConnection::MoveStudy(const std::string& targetAet, - const std::string& studyUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); - MoveInternal(targetAet, ResourceType_Study, query); - } - - void DicomUserConnection::MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); - MoveInternal(targetAet, ResourceType_Series, query); - } - - void DicomUserConnection::MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); - query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid); - MoveInternal(targetAet, ResourceType_Instance, query); - } - - - void DicomUserConnection::SetTimeout(uint32_t seconds) - { - if (seconds <= 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - dcmConnectionTimeout.set(seconds); - pimpl_->dimseTimeout_ = seconds; - pimpl_->acseTimeout_ = 10; - } - - - void DicomUserConnection::DisableTimeout() - { - /** - * Global timeout (seconds) for connecting to remote hosts. - * Default value is -1 which selects infinite timeout, i.e. blocking connect(). - */ - dcmConnectionTimeout.set(-1); - pimpl_->dimseTimeout_ = 0; - pimpl_->acseTimeout_ = 10; - } - - - void DicomUserConnection::CheckStorageSOPClassesInvariant() const - { - assert(storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() + - reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES); - } - - void DicomUserConnection::AddStorageSOPClass(const char* sop) - { - CheckStorageSOPClassesInvariant(); - - if (storageSOPClasses_.find(sop) != storageSOPClasses_.end()) - { - // This storage SOP class is already explicitly registered. Do - // nothing. - return; - } - - if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end()) - { - // This storage SOP class is not explicitly registered, but is - // used by default. Just register it explicitly. - defaultStorageSOPClasses_.erase(sop); - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - return; - } - - // This storage SOP class is neither explicitly, nor implicitly - // registered. Close the connection and register it explicitly. - - Close(); - - if (reservedStorageSOPClasses_.size() + - storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) // (*) - { - // The maximum number of SOP classes is reached - ResetStorageSOPClasses(); - defaultStorageSOPClasses_.erase(sop); - } - else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) - { - // Make room in the default storage syntaxes - assert(!defaultStorageSOPClasses_.empty()); // Necessarily true because condition (*) is false - defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); - } - - // Explicitly register the new storage syntax - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - } - -}
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomFindAnswers.h" -#include "../ServerEnumerations.h" -#include "RemoteModalityParameters.h" - -#include <stdint.h> -#include <boost/shared_ptr.hpp> -#include <boost/noncopyable.hpp> -#include <list> - -namespace Orthanc -{ - class DicomUserConnection : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - // Connection parameters - std::string preferredTransferSyntax_; - std::string localAet_; - std::string remoteAet_; - std::string remoteHost_; - uint16_t remotePort_; - ModalityManufacturer manufacturer_; - std::set<std::string> storageSOPClasses_; - std::list<std::string> reservedStorageSOPClasses_; - std::set<std::string> defaultStorageSOPClasses_; - - void CheckIsOpen() const; - - void SetupPresentationContexts(const std::string& preferredTransferSyntax); - - void MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields); - - void ResetStorageSOPClasses(); - - void CheckStorageSOPClassesInvariant() const; - - public: - DicomUserConnection(); - - ~DicomUserConnection(); - - void SetRemoteModality(const RemoteModalityParameters& parameters); - - void SetLocalApplicationEntityTitle(const std::string& aet); - - const std::string& GetLocalApplicationEntityTitle() const - { - return localAet_; - } - - void SetRemoteApplicationEntityTitle(const std::string& aet); - - const std::string& GetRemoteApplicationEntityTitle() const - { - return remoteAet_; - } - - void SetRemoteHost(const std::string& host); - - const std::string& GetRemoteHost() const - { - return remoteHost_; - } - - void SetRemotePort(uint16_t port); - - uint16_t GetRemotePort() const - { - return remotePort_; - } - - void SetRemoteManufacturer(ModalityManufacturer manufacturer); - - ModalityManufacturer GetRemoteManufacturer() const - { - return manufacturer_; - } - - void ResetPreferredTransferSyntax(); - - void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax); - - const std::string& GetPreferredTransferSyntax() const - { - return preferredTransferSyntax_; - } - - void AddStorageSOPClass(const char* sop); - - void Open(); - - void Close(); - - bool IsOpen() const; - - bool Echo(); - - void Store(const char* buffer, size_t size); - - void Store(const std::string& buffer); - - void StoreFile(const std::string& path); - - void Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& fields); - - void Move(const std::string& targetAet, - const DicomMap& findResult); - - void MovePatient(const std::string& targetAet, - const std::string& patientId); - - void MoveStudy(const std::string& targetAet, - const std::string& studyUid); - - void MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid); - - void MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid); - - void SetTimeout(uint32_t seconds); - - void DisableTimeout(); - }; -}
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../ServerEnumerations.h" - -#include <string> - -namespace Orthanc -{ - class IApplicationEntityFilter - { - public: - virtual ~IApplicationEntityFilter() - { - } - - virtual bool IsAllowedConnection(const std::string& callingIp, - const std::string& callingAet) = 0; - - virtual bool IsAllowedRequest(const std::string& callingIp, - const std::string& callingAet, - DicomRequestType type) = 0; - - virtual bool IsAllowedTransferSyntax(const std::string& callingIp, - const std::string& callingAet, - TransferSyntax syntax) = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomFindAnswers.h" - -#include <vector> -#include <string> - - -namespace Orthanc -{ - class IFindRequestHandler - { - public: - virtual ~IFindRequestHandler() - { - } - - /** - * Can throw exceptions. Returns "false" iff too many results have - * to be returned. In such a case, a "Matching terminated due to - * Cancel request" DIMSE code would be returned. - * https://www.dabsoft.ch/dicom/4/V.4.1/ - **/ - virtual bool Handle(DicomFindAnswers& answers, - const DicomMap& input, - const std::string& remoteIp, - const std::string& remoteAet) = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IFindRequestHandler.h" - -namespace Orthanc -{ - class IFindRequestHandlerFactory - { - public: - virtual ~IFindRequestHandlerFactory() - { - } - - virtual IFindRequestHandler* ConstructFindRequestHandler() = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/DicomFormat/DicomMap.h" - -#include <vector> -#include <string> - - -namespace Orthanc -{ - class IMoveRequestIterator - { - public: - enum Status - { - Status_Success, - Status_Failure, - Status_Warning - }; - - virtual ~IMoveRequestIterator() - { - } - - virtual unsigned int GetSubOperationCount() const = 0; - - virtual Status DoNext() = 0; - }; - - - class IMoveRequestHandler - { - public: - virtual ~IMoveRequestHandler() - { - } - - virtual IMoveRequestIterator* Handle(const std::string& target, - const DicomMap& input, - const std::string& remoteIp, - const std::string& remoteAet) = 0; - }; - -}
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IMoveRequestHandler.h" - -namespace Orthanc -{ - class IMoveRequestHandlerFactory - { - public: - virtual ~IMoveRequestHandlerFactory() - { - } - - virtual IMoveRequestHandler* ConstructMoveRequestHandler() = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/DicomFormat/DicomMap.h" - -#include <vector> -#include <string> -#include <json/json.h> - -namespace Orthanc -{ - class IStoreRequestHandler - { - public: - virtual ~IStoreRequestHandler() - { - } - - virtual void Handle(const std::string& dicomFile, - const DicomMap& dicomSummary, - const Json::Value& dicomJson, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IStoreRequestHandler.h" - -namespace Orthanc -{ - class IStoreRequestHandlerFactory - { - public: - virtual ~IStoreRequestHandlerFactory() - { - } - - virtual IStoreRequestHandler* ConstructStoreRequestHandler() = 0; - }; -}
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "RemoteModalityParameters.h" - -#include "../../Core/OrthancException.h" - -#include <boost/lexical_cast.hpp> -#include <stdexcept> - -namespace Orthanc -{ - RemoteModalityParameters::RemoteModalityParameters() : - aet_("ORTHANC"), - host_("localhost"), - port_(104), - manufacturer_(ModalityManufacturer_Generic) - { - } - - RemoteModalityParameters::RemoteModalityParameters(const std::string& aet, - const std::string& host, - uint16_t port, - ModalityManufacturer manufacturer) - { - SetApplicationEntityTitle(aet); - SetHost(host); - SetPort(port); - SetManufacturer(manufacturer); - } - - - void RemoteModalityParameters::FromJson(const Json::Value& modality) - { - if (!modality.isArray() || - (modality.size() != 3 && modality.size() != 4)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - SetApplicationEntityTitle(modality.get(0u, "").asString()); - SetHost(modality.get(1u, "").asString()); - - const Json::Value& portValue = modality.get(2u, ""); - try - { - int tmp = portValue.asInt(); - - if (tmp <= 0 || tmp >= 65535) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - SetPort(static_cast<uint16_t>(tmp)); - } - catch (std::runtime_error /* error inside JsonCpp */) - { - try - { - SetPort(boost::lexical_cast<uint16_t>(portValue.asString())); - } - catch (boost::bad_lexical_cast) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - if (modality.size() == 4) - { - SetManufacturer(modality.get(3u, "").asString()); - } - else - { - SetManufacturer(ModalityManufacturer_Generic); - } - } - - void RemoteModalityParameters::ToJson(Json::Value& value) const - { - value = Json::arrayValue; - value.append(GetApplicationEntityTitle()); - value.append(GetHost()); - value.append(GetPort()); - value.append(EnumerationToString(GetManufacturer())); - } -}
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../ServerEnumerations.h" - -#include <stdint.h> -#include <string> -#include <json/json.h> - -namespace Orthanc -{ - class RemoteModalityParameters - { - private: - std::string aet_; - std::string host_; - uint16_t port_; - ModalityManufacturer manufacturer_; - - public: - RemoteModalityParameters(); - - RemoteModalityParameters(const std::string& aet, - const std::string& host, - uint16_t port, - ModalityManufacturer manufacturer); - - const std::string& GetApplicationEntityTitle() const - { - return aet_; - } - - void SetApplicationEntityTitle(const std::string& aet) - { - aet_ = aet; - } - - const std::string& GetHost() const - { - return host_; - } - - void SetHost(const std::string& host) - { - host_ = host; - } - - uint16_t GetPort() const - { - return port_; - } - - void SetPort(uint16_t port) - { - port_ = port; - } - - ModalityManufacturer GetManufacturer() const - { - return manufacturer_; - } - - void SetManufacturer(ModalityManufacturer manufacturer) - { - manufacturer_ = manufacturer; - } - - void SetManufacturer(const std::string& manufacturer) - { - manufacturer_ = StringToModalityManufacturer(manufacturer); - } - - void FromJson(const Json::Value& modality); - - void ToJson(Json::Value& value) const; - }; -}
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ReusableDicomUserConnection.h" - -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" - -namespace Orthanc -{ - static boost::posix_time::ptime Now() - { - return boost::posix_time::microsec_clock::local_time(); - } - - void ReusableDicomUserConnection::Open(const std::string& localAet, - const RemoteModalityParameters& remote) - { - if (connection_ != NULL && - connection_->GetLocalApplicationEntityTitle() == localAet && - connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && - connection_->GetRemoteHost() == remote.GetHost() && - connection_->GetRemotePort() == remote.GetPort() && - connection_->GetRemoteManufacturer() == remote.GetManufacturer()) - { - // The current connection can be reused - LOG(INFO) << "Reusing the previous SCU connection"; - return; - } - - Close(); - - connection_ = new DicomUserConnection(); - connection_->SetLocalApplicationEntityTitle(localAet); - connection_->SetRemoteModality(remote); - connection_->Open(); - } - - void ReusableDicomUserConnection::Close() - { - if (connection_ != NULL) - { - delete connection_; - connection_ = NULL; - } - } - - void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) - { - for (;;) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - if (!that->continue_) - { - //LOG(INFO) << "Finishing the thread watching the global SCU connection"; - return; - } - - { - boost::mutex::scoped_lock lock(that->mutex_); - if (that->connection_ != NULL && - Now() >= that->lastUse_ + that->timeBeforeClose_) - { - LOG(INFO) << "Closing the global SCU connection after timeout"; - that->Close(); - } - } - } - } - - - ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote) : - ::Orthanc::Locker(that) - { - that.Open(localAet, remote); - connection_ = that.connection_; - } - - - DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() - { - if (connection_ == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *connection_; - } - - ReusableDicomUserConnection::ReusableDicomUserConnection() : - connection_(NULL), - timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds - { - lastUse_ = Now(); - continue_ = true; - closeThread_ = boost::thread(CloseThread, this); - } - - ReusableDicomUserConnection::~ReusableDicomUserConnection() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Finalize(); - } - } - - void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) - { - boost::mutex::scoped_lock lock(mutex_); - - if (ms == 0) - { - ms = 1; - } - - timeBeforeClose_ = boost::posix_time::milliseconds(ms); - } - - void ReusableDicomUserConnection::Lock() - { - mutex_.lock(); - } - - void ReusableDicomUserConnection::Unlock() - { - if (connection_ != NULL && - connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) - { - // "storescp" from DCMTK has problems when reusing a - // connection. Always close. - Close(); - } - - lastUse_ = Now(); - mutex_.unlock(); - } - - - void ReusableDicomUserConnection::Finalize() - { - if (continue_) - { - continue_ = false; - - if (closeThread_.joinable()) - { - closeThread_.join(); - } - - Close(); - } - } -} -
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomUserConnection.h" -#include "../../Core/MultiThreading/Locker.h" - -#include <boost/thread.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> - -namespace Orthanc -{ - class ReusableDicomUserConnection : public ILockable - { - private: - boost::mutex mutex_; - DicomUserConnection* connection_; - bool continue_; - boost::posix_time::time_duration timeBeforeClose_; - boost::posix_time::ptime lastUse_; - boost::thread closeThread_; - - void Open(const std::string& localAet, - const RemoteModalityParameters& remote); - - void Close(); - - static void CloseThread(ReusableDicomUserConnection* that); - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - class Locker : public ::Orthanc::Locker - { - private: - DicomUserConnection* connection_; - - public: - Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote); - - DicomUserConnection& GetConnection(); - }; - - ReusableDicomUserConnection(); - - virtual ~ReusableDicomUserConnection(); - - void SetMillisecondsBeforeClose(uint64_t ms); - - void Finalize(); - }; -} -
--- a/OrthancServer/ExportedResource.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ExportedResource.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ExportedResource.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/FromDcmtkBridge.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1451 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -#include "PrecompiledHeadersServer.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "Internals/DicomImageDecoder.h" - -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" -#include "OrthancInitialization.h" -#include "../Core/Logging.h" -#include "../Core/Toolbox.h" -#include "../Core/OrthancException.h" -#include "../Core/Images/PngWriter.h" -#include "../Core/Uuid.h" -#include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" - -#include <list> -#include <limits> - -#include <boost/lexical_cast.hpp> -#include <boost/filesystem.hpp> - -#include <dcmtk/dcmdata/dcchrstr.h> -#include <dcmtk/dcmdata/dcdicent.h> -#include <dcmtk/dcmdata/dcdict.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcdeftag.h> - -#include <dcmtk/dcmdata/dcvrae.h> -#include <dcmtk/dcmdata/dcvras.h> -#include <dcmtk/dcmdata/dcvrcs.h> -#include <dcmtk/dcmdata/dcvrda.h> -#include <dcmtk/dcmdata/dcvrds.h> -#include <dcmtk/dcmdata/dcvrdt.h> -#include <dcmtk/dcmdata/dcvrfd.h> -#include <dcmtk/dcmdata/dcvrfl.h> -#include <dcmtk/dcmdata/dcvris.h> -#include <dcmtk/dcmdata/dcvrlo.h> -#include <dcmtk/dcmdata/dcvrlt.h> -#include <dcmtk/dcmdata/dcvrpn.h> -#include <dcmtk/dcmdata/dcvrsh.h> -#include <dcmtk/dcmdata/dcvrsl.h> -#include <dcmtk/dcmdata/dcvrss.h> -#include <dcmtk/dcmdata/dcvrst.h> -#include <dcmtk/dcmdata/dcvrtm.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcvrul.h> -#include <dcmtk/dcmdata/dcvrus.h> -#include <dcmtk/dcmdata/dcvrut.h> -#include <dcmtk/dcmdata/dcpixel.h> -#include <dcmtk/dcmdata/dcpixseq.h> -#include <dcmtk/dcmdata/dcpxitem.h> -#include <dcmtk/dcmdata/dcvrat.h> - -#include <dcmtk/dcmnet/dul.h> - -#include <boost/math/special_functions/round.hpp> -#include <boost/algorithm/string/predicate.hpp> -#include <dcmtk/dcmdata/dcostrmb.h> - - -namespace Orthanc -{ - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, - EmbeddedResources::FileResourceId resource) - { - Toolbox::TemporaryFile tmp; - - FILE* fp = fopen(tmp.GetPath().c_str(), "wb"); - fwrite(EmbeddedResources::GetFileResourceBuffer(resource), - EmbeddedResources::GetFileResourceSize(resource), 1, fp); - fclose(fp); - - if (!dictionary.loadDictionary(tmp.GetPath().c_str())) - { - throw OrthancException(ErrorCode_InternalError); - } - } - -#else - static void LoadExternalDictionary(DcmDataDictionary& dictionary, - const std::string& directory, - const std::string& filename) - { - boost::filesystem::path p = directory; - p = p / filename; - - LOG(WARNING) << "Loading the external DICOM dictionary " << p; - - if (!dictionary.loadDictionary(p.string().c_str())) - { - throw OrthancException(ErrorCode_InternalError); - } - } - -#endif - - - namespace - { - class DictionaryLocker - { - private: - DcmDataDictionary& dictionary_; - - public: - DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) - { - } - - ~DictionaryLocker() - { - dcmDataDict.unlock(); - } - - DcmDataDictionary& operator*() - { - return dictionary_; - } - - DcmDataDictionary* operator->() - { - return &dictionary_; - } - }; - } - - - void FromDcmtkBridge::InitializeDictionary() - { - /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ - dcmDisableGethostbyaddr.set(OFTrue); - - { - DictionaryLocker locker; - - locker->clear(); - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - LOG(WARNING) << "Loading the embedded dictionaries"; - /** - * Do not load DICONDE dictionary, it breaks the other tags. The - * command "strace storescu 2>&1 |grep dic" shows that DICONDE - * dictionary is not loaded by storescu. - **/ - //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE); - - LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM); - LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE); - -#elif defined(__linux) || defined(__FreeBSD_kernel__) - std::string path = DCMTK_DICTIONARY_DIR; - - const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); - if (env != NULL) - { - path = std::string(env); - } - - LoadExternalDictionary(*locker, path, "dicom.dic"); - LoadExternalDictionary(*locker, path, "private.dic"); - -#else -#error Support your platform here -#endif - } - - /* make sure data dictionary is loaded */ - if (!dcmDataDict.isDictionaryLoaded()) - { - LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; - throw OrthancException(ErrorCode_InternalError); - } - - { - // Test the dictionary with a simple DICOM tag - DcmTag key(0x0010, 0x1030); // This is PatientWeight - if (key.getEVR() != EVR_DS) - { - LOG(ERROR) << "The DICOM dictionary has not been correctly read"; - throw OrthancException(ErrorCode_InternalError); - } - } - } - - - void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, - const DcmEVR& vr, - const std::string& name, - unsigned int minMultiplicity, - unsigned int maxMultiplicity) - { - if (minMultiplicity < 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (maxMultiplicity == 0) - { - maxMultiplicity = DcmVariableVM; - } - else if (maxMultiplicity < minMultiplicity) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - std::auto_ptr<DcmDictEntry> entry(new DcmDictEntry(tag.GetGroup(), - tag.GetElement(), - vr, name.c_str(), - static_cast<int>(minMultiplicity), - static_cast<int>(maxMultiplicity), - NULL /* version */, - OFTrue /* doCopyString */, - NULL /* private creator */)); - - entry->setGroupRangeRestriction(DcmDictRange_Unspecified); - entry->setElementRangeRestriction(DcmDictRange_Unspecified); - - { - DictionaryLocker locker; - locker->addEntry(entry.release()); - } - } - - - Encoding FromDcmtkBridge::DetectEncoding(DcmDataset& dataset) - { - // By default, Latin1 encoding is assumed - std::string s = Configuration::GetGlobalStringParameter("DefaultEncoding", "Latin1"); - Encoding encoding = s.empty() ? Encoding_Latin1 : StringToEncoding(s.c_str()); - - OFString tmp; - if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) - { - std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); - - if (characterSet.empty()) - { - // Empty specific character set tag: Use the default encoding - } - else if (GetDicomEncoding(encoding, characterSet.c_str())) - { - // The specific character set is supported by the Orthanc core - } - else - { - LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet - << ", fallback to ASCII (remove all special characters)"; - encoding = Encoding_Ascii; - } - } - else - { - // No specific character set tag: Use the default encoding - } - - return encoding; - } - - - void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) - { - Encoding encoding = DetectEncoding(dataset); - - target.Clear(); - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - if (element && element->isLeaf()) - { - target.SetValue(element->getTag().getGTag(), - element->getTag().getETag(), - ConvertLeafElement(*element, DicomToJsonFlags_Default, encoding)); - } - } - } - - - DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) - { - return DicomTag(tag.getGTag(), tag.getETag()); - } - - - DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) - { - return DicomTag(element.getGTag(), element.getETag()); - } - - - bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag) - { -#if 1 - DcmTagKey tmp(tag.GetGroup(), tag.GetElement()); - return tmp.isPrivate(); -#else - // Implementation for Orthanc versions <= 0.8.5 - DcmTag tmp(tag.GetGroup(), tag.GetElement()); - return IsPrivateTag(tmp); -#endif - } - - - DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, - DicomToJsonFlags flags, - Encoding encoding) - { - if (!element.isLeaf()) - { - // This function is only applicable to leaf elements - throw OrthancException(ErrorCode_BadParameterType); - } - - if (element.isaString()) - { - char *c; - if (element.getString(c).good()) - { - if (c == NULL) // This case corresponds to the empty string - { - return new DicomValue("", false); - } - else - { - std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); - return new DicomValue(utf8, false); - } - } - else - { - return new DicomValue; - } - } - - try - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - switch (element.getVR()) - { - - /** - * Deal with binary data (including PixelData). - **/ - - 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 - { - if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) - { - Uint8* data = NULL; - if (element.getUint8Array(data) == EC_Normal) - { - return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true); - } - } - - return new DicomValue; - } - - /** - * String types, should never happen at this point because of - * "element.isaString()". - **/ - - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_AS: // age string - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - return new DicomValue; - - - /** - * Numberic types - **/ - - case EVR_SL: // signed long - { - Sint32 f; - if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_SS: // signed short - { - Sint16 f; - if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_UL: // unsigned long - { - Uint32 f; - if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_US: // unsigned short - { - Uint16 f; - if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_FL: // float single-precision - { - Float32 f; - if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_FD: // float double-precision - { - Float64 f; - if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - - /** - * Attribute tag. - **/ - - case EVR_AT: - { - DcmTagKey tag; - if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good()) - { - DicomTag t(tag.getGroup(), tag.getElement()); - return new DicomValue(t.Format(), false); - } - else - { - return new DicomValue; - } - } - - - /** - * Sequence types, should never occur at this point because of - * "element.isLeaf()". - **/ - - case EVR_SQ: // sequence of items - return new DicomValue; - - - /** - * 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 - return new DicomValue; - - - /** - * Default case. - **/ - - default: - return new DicomValue; - } - } - catch (boost::bad_lexical_cast) - { - return new DicomValue; - } - catch (std::bad_cast) - { - return new DicomValue; - } - } - - - static Json::Value& PrepareNode(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format) - { - assert(parent.type() == Json::objectValue); - - DicomTag tag(FromDcmtkBridge::GetTag(element)); - const std::string formattedTag = tag.Format(); - - if (format == DicomToJsonFormat_Short) - { - parent[formattedTag] = Json::nullValue; - return parent[formattedTag]; - } - - // This code gives access to the name of the private tags - DcmTag tagbis(element.getTag()); - const std::string tagName(tagbis.getTagName()); - - switch (format) - { - case DicomToJsonFormat_Simple: - parent[tagName] = Json::nullValue; - return parent[tagName]; - - case DicomToJsonFormat_Full: - { - parent[formattedTag] = Json::objectValue; - Json::Value& node = parent[formattedTag]; - - if (element.isLeaf()) - { - node["Name"] = tagName; - - if (tagbis.getPrivateCreator() != NULL) - { - node["PrivateCreator"] = tagbis.getPrivateCreator(); - } - - return node; - } - else - { - node["Name"] = tagName; - node["Type"] = "Sequence"; - node["Value"] = Json::nullValue; - return node["Value"]; - } - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - static void LeafValueToJson(Json::Value& target, - const DicomValue& value, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - Json::Value* targetValue = NULL; - Json::Value* targetType = NULL; - - switch (format) - { - case DicomToJsonFormat_Short: - case DicomToJsonFormat_Simple: - { - assert(target.type() == Json::nullValue); - targetValue = ⌖ - break; - } - - case DicomToJsonFormat_Full: - { - assert(target.type() == Json::objectValue); - target["Value"] = Json::nullValue; - target["Type"] = Json::nullValue; - targetType = &target["Type"]; - targetValue = &target["Value"]; - break; - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(targetValue != NULL); - assert(targetValue->type() == Json::nullValue); - assert(targetType == NULL || targetType->type() == Json::nullValue); - - if (value.IsNull()) - { - if (targetType != NULL) - { - *targetType = "Null"; - } - } - else if (value.IsBinary()) - { - if (flags & DicomToJsonFlags_ConvertBinaryToAscii) - { - *targetValue = Toolbox::ConvertToAscii(value.GetContent()); - } - else - { - std::string s; - value.FormatDataUriScheme(s); - *targetValue = s; - } - - if (targetType != NULL) - { - *targetType = "Binary"; - } - } - else if (maxStringLength == 0 || - value.GetContent().size() <= maxStringLength) - { - *targetValue = value.GetContent(); - - if (targetType != NULL) - { - *targetType = "String"; - } - } - else - { - if (targetType != NULL) - { - *targetType = "TooLong"; - } - } - } - - - static void DatasetToJson(Json::Value& parent, - DcmItem& item, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding); - - - void FromDcmtkBridge::ToJson(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding) - { - if (parent.type() == Json::nullValue) - { - parent = Json::objectValue; - } - - assert(parent.type() == Json::objectValue); - Json::Value& target = PrepareNode(parent, element, format); - - if (element.isLeaf()) - { - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, encoding)); - LeafValueToJson(target, *v, format, flags, maxStringLength); - } - else - { - assert(target.type() == Json::nullValue); - target = Json::arrayValue; - - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following dynamic_cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); - - for (unsigned long i = 0; i < sequence.card(); i++) - { - DcmItem* child = sequence.getItem(i); - Json::Value& v = target.append(Json::objectValue); - DatasetToJson(v, *child, format, flags, maxStringLength, encoding); - } - } - } - - - static void DatasetToJson(Json::Value& parent, - DcmItem& item, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding) - { - assert(parent.type() == Json::objectValue); - - for (unsigned long i = 0; i < item.card(); i++) - { - DcmElement* element = item.getElement(i); - if (element == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - if (!(flags & DicomToJsonFlags_IncludePrivateTags) && - element->getTag().isPrivate()) - { - continue; - } - - if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) - { - DictionaryLocker locker; - if (locker->findEntry(element->getTag(), NULL) == NULL) - { - continue; - } - } - - DcmEVR evr = element->getTag().getEVR(); - if (evr == EVR_OB || - evr == EVR_OF || - evr == EVR_OW || - evr == EVR_UN || - evr == EVR_ox) - { - // This is a binary tag - DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); - - if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || - (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) - { - continue; - } - } - - FromDcmtkBridge::ToJson(parent, *element, format, flags, maxStringLength, encoding); - } - } - - - void FromDcmtkBridge::ToJson(Json::Value& target, - DcmDataset& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - target = Json::objectValue; - DatasetToJson(target, dataset, format, flags, maxStringLength, DetectEncoding(dataset)); - } - - - std::string FromDcmtkBridge::GetName(const DicomTag& t) - { - // Some patches for important tags because of different DICOM - // dictionaries between DCMTK versions - std::string n = t.GetMainTagsName(); - if (n.size() != 0) - { - return n; - } - // End of patches - -#if 0 - DcmTagKey tag(t.GetGroup(), t.GetElement()); - const DcmDataDictionary& dict = dcmDataDict.rdlock(); - const DcmDictEntry* entry = dict.findEntry(tag, NULL); - - std::string s(DcmTag_ERROR_TagName); - if (entry != NULL) - { - s = std::string(entry->getTagName()); - } - - dcmDataDict.unlock(); - return s; -#else - DcmTag tag(t.GetGroup(), t.GetElement()); - const char* name = tag.getTagName(); - if (name == NULL) - { - return DcmTag_ERROR_TagName; - } - else - { - return std::string(name); - } -#endif - } - - - DicomTag FromDcmtkBridge::ParseTag(const char* name) - { - if (strlen(name) == 9 && - isxdigit(name[0]) && - isxdigit(name[1]) && - isxdigit(name[2]) && - isxdigit(name[3]) && - (name[4] == '-' || name[4] == ',') && - isxdigit(name[5]) && - isxdigit(name[6]) && - isxdigit(name[7]) && - isxdigit(name[8])) - { - uint16_t group = GetTagValue(name); - uint16_t element = GetTagValue(name + 5); - return DicomTag(group, element); - } - -#if 0 - const DcmDataDictionary& dict = dcmDataDict.rdlock(); - const DcmDictEntry* entry = dict.findEntry(name); - - if (entry == NULL) - { - dcmDataDict.unlock(); - throw OrthancException(ErrorCode_UnknownDicomTag); - } - else - { - DcmTagKey key = entry->getKey(); - DicomTag tag(key.getGroup(), key.getElement()); - dcmDataDict.unlock(); - return tag; - } -#else - DcmTag tag; - if (DcmTag::findTagFromName(name, tag).good()) - { - return DicomTag(tag.getGTag(), tag.getETag()); - } - else - { - throw OrthancException(ErrorCode_UnknownDicomTag); - } -#endif - } - - - bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) - { - DcmTag tmp(tag.GetGroup(), tag.GetElement()); - return tmp.isUnknownVR(); - } - - - void FromDcmtkBridge::ToJson(Json::Value& result, - const DicomMap& values, - bool simplify) - { - if (result.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - result.clear(); - - for (DicomMap::Map::const_iterator - it = values.map_.begin(); it != values.map_.end(); ++it) - { - if (simplify) - { - if (it->second->IsNull()) - { - result[GetName(it->first)] = Json::nullValue; - } - else - { - // TODO IsBinary - result[GetName(it->first)] = it->second->GetContent(); - } - } - else - { - Json::Value value = Json::objectValue; - - value["Name"] = GetName(it->first); - - if (it->second->IsNull()) - { - value["Type"] = "Null"; - value["Value"] = Json::nullValue; - } - else - { - // TODO IsBinary - value["Type"] = "String"; - value["Value"] = it->second->GetContent(); - } - - result[it->first.Format()] = value; - } - } - } - - - std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) - { - char uid[100]; - - switch (level) - { - case ResourceType_Patient: - // The "PatientID" field is of type LO (Long String), 64 - // Bytes Maximum. An UUID is of length 36, thus it can be used - // as a random PatientID. - return Toolbox::GenerateUuid(); - - case ResourceType_Instance: - return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); - - case ResourceType_Series: - return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); - - case ResourceType_Study: - return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet) - { - // 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 - uint32_t s = ff.calcElementLength(xfer, encodingType); - buffer.resize(s); - DcmOutputBufferStream ob(&buffer[0], s); - - // 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(); - - // Handle errors - if (c.good()) - { - return true; - } - else - { - buffer.clear(); - return false; - } - } - - - ValueRepresentation FromDcmtkBridge::GetValueRepresentation(const DicomTag& tag) - { - DcmTag t(tag.GetGroup(), tag.GetElement()); - switch (t.getEVR()) - { - case EVR_PN: - return ValueRepresentation_PatientName; - - case EVR_DA: - return ValueRepresentation_Date; - - case EVR_DT: - return ValueRepresentation_DateTime; - - case EVR_TM: - return ValueRepresentation_Time; - - default: - return ValueRepresentation_Other; - } - } - - - static bool IsBinaryTag(const DcmTag& key) - { - return (key.isPrivate() || - key.isUnknownVR() || - 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 (IsBinaryTag(key)) - { - return new DcmOtherByteOtherWord(key); - } - - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * Binary types, handled above - **/ - - 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); - - - /** - * 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); - } - - - - void FromDcmtkBridge::FillElementWithString(DcmElement& element, - const DicomTag& tag, - const std::string& utf8Value, - bool decodeBinaryTags, - Encoding dicomEncoding) - { - std::string binary; - const std::string* decoded = &utf8Value; - - if (decodeBinaryTags && - boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) - { - std::string mime; - Toolbox::DecodeDataUriScheme(mime, binary, utf8Value); - decoded = &binary; - } - else if (dicomEncoding != Encoding_Utf8) - { - binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); - decoded = &binary; - } - - DcmTag key(tag.GetGroup(), tag.GetElement()); - - if (IsBinaryTag(key)) - { - if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good()) - { - return; - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - } - - bool ok = false; - - try - { - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * TODO. - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - case EVR_UN: // unknown value representation - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * String types. - **/ - - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_AS: // age string - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - { - ok = element.putString(decoded->c_str()).good(); - break; - } - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - { - ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good(); - break; - } - - case EVR_SS: // signed short - { - ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good(); - break; - } - - case EVR_UL: // unsigned long - { - ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good(); - break; - } - - case EVR_US: // unsigned short - { - ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good(); - break; - } - - case EVR_FL: // float single-precision - { - ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good(); - break; - } - - case EVR_FD: // float double-precision - { - ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good(); - break; - } - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - { - ok = false; - break; - } - - - /** - * Internal to DCMTK. - **/ - - case EVR_ox: // OB or OW depending on context - 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; - } - } - catch (boost::bad_lexical_cast&) - { - ok = false; - } - - if (!ok) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, - const Json::Value& value, - bool decodeBinaryTags, - Encoding dicomEncoding) - { - std::auto_ptr<DcmElement> element; - - switch (value.type()) - { - case Json::stringValue: - element.reset(CreateElementForTag(tag)); - FillElementWithString(*element, tag, value.asString(), decodeBinaryTags, dicomEncoding); - break; - - case Json::arrayValue: - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - if (key.getEVR() != EVR_SQ) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key, value.size()); - element.reset(sequence); - - for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) - { - std::auto_ptr<DcmItem> item(new DcmItem); - - 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]], decodeBinaryTags, dicomEncoding)); - } - - sequence->append(item.release()); - } - - break; - } - - default: - throw OrthancException(ErrorCode_BadParameterType); - } - - return element.release(); - } -}
--- a/OrthancServer/FromDcmtkBridge.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerEnumerations.h" - -#include "../Core/DicomFormat/DicomMap.h" - -#include <dcmtk/dcmdata/dcdatset.h> -#include <json/json.h> - -namespace Orthanc -{ - class FromDcmtkBridge - { - public: - static void InitializeDictionary(); - - static void RegisterDictionaryTag(const DicomTag& tag, - const DcmEVR& vr, - const std::string& name, - unsigned int minMultiplicity, - unsigned int maxMultiplicity); - - static Encoding DetectEncoding(DcmDataset& dataset); - - static void Convert(DicomMap& target, DcmDataset& dataset); - - static DicomTag Convert(const DcmTag& tag); - - static DicomTag GetTag(const DcmElement& element); - - static bool IsPrivateTag(const DicomTag& tag); - - static bool IsUnknownTag(const DicomTag& tag); - - static DicomValue* ConvertLeafElement(DcmElement& element, - DicomToJsonFlags flags, - Encoding encoding); - - static void ToJson(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding dicomEncoding); - - static void ToJson(Json::Value& target, - DcmDataset& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); - - static std::string GetName(const DicomTag& tag); - - static DicomTag ParseTag(const char* name); - - static DicomTag ParseTag(const std::string& name) - { - return ParseTag(name.c_str()); - } - - static bool HasTag(const DicomMap& fields, - const std::string& tagName) - { - return fields.HasTag(ParseTag(tagName)); - } - - static const DicomValue& GetValue(const DicomMap& fields, - const std::string& tagName) - { - return fields.GetValue(ParseTag(tagName)); - } - - static void SetValue(DicomMap& target, - const std::string& tagName, - DicomValue* value) - { - target.SetValue(ParseTag(tagName), value); - } - - static void ToJson(Json::Value& result, - const DicomMap& values, - bool simplify); - - static std::string GenerateUniqueIdentifier(ResourceType level); - - static bool SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet); - - static ValueRepresentation GetValueRepresentation(const DicomTag& tag); - - static DcmElement* CreateElementForTag(const DicomTag& tag); - - static void FillElementWithString(DcmElement& element, - const DicomTag& tag, - const std::string& utf8alue, // Encoded using UTF-8 - bool interpretBinaryTags, - Encoding dicomEncoding); - - static DcmElement* FromJson(const DicomTag& tag, - const Json::Value& element, // Encoding using UTF-8 - bool interpretBinaryTags, - Encoding dicomEncoding); - }; -}
--- a/OrthancServer/IDatabaseListener.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/IDatabaseListener.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/IDatabaseWrapper.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/IDatabaseWrapper.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -155,6 +156,12 @@ IdentifierConstraintType type, const std::string& value) = 0; + virtual void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) = 0; + virtual bool LookupMetadata(std::string& target, int64_t id, MetadataType type) = 0;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/IDicomImageDecoder.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/Images/ImageAccessor.h" + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class IDicomImageDecoder : public boost::noncopyable + { + public: + virtual ~IDicomImageDecoder() + { + } + + virtual ImageAccessor* Decode(const void* dicom, + size_t size, + unsigned int frame) = 0; + }; +}
--- a/OrthancServer/IServerListener.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/IServerListener.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +40,7 @@ namespace Orthanc { - class IServerListener + class IServerListener : public boost::noncopyable { public: virtual ~IServerListener() @@ -48,8 +49,8 @@ virtual void SignalStoredInstance(const std::string& publicId, DicomInstanceToStore& instance, - const Json::Value& simplifiedTags) = 0; - + const Json::Value& simplifiedTags) = 0; + virtual void SignalChange(const ServerIndexChange& change) = 0; virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance,
--- a/OrthancServer/Internals/CommandDispatcher.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,877 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "CommandDispatcher.h" - -#include "FindScp.h" -#include "StoreScp.h" -#include "MoveScp.h" -#include "../../Core/Toolbox.h" -#include "../../Core/Logging.h" - -#include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ -#include <boost/lexical_cast.hpp> - -#define ORTHANC_PROMISCUOUS 1 - -static OFBool opt_rejectWithoutImplementationUID = OFFalse; - - - -#if ORTHANC_PROMISCUOUS == 1 -static -DUL_PRESENTATIONCONTEXT * -findPresentationContextID(LST_HEAD * head, - T_ASC_PresentationContextID presentationContextID) -{ - DUL_PRESENTATIONCONTEXT *pc; - LST_HEAD **l; - OFBool found = OFFalse; - - if (head == NULL) - return NULL; - - l = &head; - if (*l == NULL) - return NULL; - - pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l)); - (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc)); - - while (pc && !found) { - if (pc->presentationContextID == presentationContextID) { - found = OFTrue; - } else { - pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l)); - } - } - return pc; -} - - -/** accept all presenstation contexts for unknown SOP classes, - * i.e. UIDs appearing in the list of abstract syntaxes - * where no corresponding name is defined in the UID dictionary. - * @param params pointer to association parameters structure - * @param transferSyntax transfer syntax to accept - * @param acceptedRole SCU/SCP role to accept - */ -static OFCondition acceptUnknownContextsWithTransferSyntax( - T_ASC_Parameters * params, - const char* transferSyntax, - T_ASC_SC_ROLE acceptedRole) -{ - OFCondition cond = EC_Normal; - int n, i, k; - DUL_PRESENTATIONCONTEXT *dpc; - T_ASC_PresentationContext pc; - OFBool accepted = OFFalse; - OFBool abstractOK = OFFalse; - - n = ASC_countPresentationContexts(params); - for (i = 0; i < n; i++) - { - cond = ASC_getPresentationContext(params, i, &pc); - if (cond.bad()) return cond; - abstractOK = OFFalse; - accepted = OFFalse; - - if (dcmFindNameOfUID(pc.abstractSyntax) == NULL) - { - abstractOK = OFTrue; - - /* check the transfer syntax */ - for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++) - { - if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0) - { - accepted = OFTrue; - } - } - } - - if (accepted) - { - cond = ASC_acceptPresentationContext( - params, pc.presentationContextID, - transferSyntax, acceptedRole); - if (cond.bad()) return cond; - } else { - T_ASC_P_ResultReason reason; - - /* do not refuse if already accepted */ - dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext, - pc.presentationContextID); - if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))) - { - - if (abstractOK) { - reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; - } else { - reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED; - } - /* - * If previously this presentation context was refused - * because of bad transfer syntax let it stay that way. - */ - if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED)) - reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; - - cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason); - if (cond.bad()) return cond; - } - } - } - return EC_Normal; -} - - -/** accept all presenstation contexts for unknown SOP classes, - * i.e. UIDs appearing in the list of abstract syntaxes - * where no corresponding name is defined in the UID dictionary. - * This method is passed a list of "preferred" transfer syntaxes. - * @param params pointer to association parameters structure - * @param transferSyntax transfer syntax to accept - * @param acceptedRole SCU/SCP role to accept - */ -static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes( - T_ASC_Parameters * params, - const char* transferSyntaxes[], int transferSyntaxCount, - T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT) -{ - OFCondition cond = EC_Normal; - /* - ** Accept in the order "least wanted" to "most wanted" transfer - ** syntax. Accepting a transfer syntax will override previously - ** accepted transfer syntaxes. - */ - for (int i = transferSyntaxCount - 1; i >= 0; i--) - { - cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole); - if (cond.bad()) return cond; - } - return cond; -} -#endif - - - -namespace Orthanc -{ - namespace Internals - { - /** - * EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0 - * (dcmAllStorageSOPClassUIDs). - * - * an array of const strings containing all known Storage SOP - * Classes that fit into the conventional - * PATIENT-STUDY-SERIES-INSTANCE information model, - * i.e. everything a Storage SCP might want to store in a PACS. - * Special cases such as hanging protocol storage or the Storage - * SOP Class are not included in this list. - * - * THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED - * ONES AND IS LARGER THAN 64 ENTRIES. - */ - - const char* orthancStorageSOPClassUIDs[] = - { - UID_AmbulatoryECGWaveformStorage, - UID_ArterialPulseWaveformStorage, - UID_AutorefractionMeasurementsStorage, - UID_BasicStructuredDisplayStorage, - UID_BasicTextSRStorage, - UID_BasicVoiceAudioWaveformStorage, - UID_BlendingSoftcopyPresentationStateStorage, - UID_BreastTomosynthesisImageStorage, - UID_CardiacElectrophysiologyWaveformStorage, - UID_ChestCADSRStorage, - UID_ColonCADSRStorage, - UID_ColorSoftcopyPresentationStateStorage, - UID_ComprehensiveSRStorage, - UID_ComputedRadiographyImageStorage, - UID_CTImageStorage, - UID_DeformableSpatialRegistrationStorage, - UID_DigitalIntraOralXRayImageStorageForPresentation, - UID_DigitalIntraOralXRayImageStorageForProcessing, - UID_DigitalMammographyXRayImageStorageForPresentation, - UID_DigitalMammographyXRayImageStorageForProcessing, - UID_DigitalXRayImageStorageForPresentation, - UID_DigitalXRayImageStorageForProcessing, - UID_EncapsulatedCDAStorage, - UID_EncapsulatedPDFStorage, - UID_EnhancedCTImageStorage, - UID_EnhancedMRColorImageStorage, - UID_EnhancedMRImageStorage, - UID_EnhancedPETImageStorage, - UID_EnhancedSRStorage, - UID_EnhancedUSVolumeStorage, - UID_EnhancedXAImageStorage, - UID_EnhancedXRFImageStorage, - UID_GeneralAudioWaveformStorage, - UID_GeneralECGWaveformStorage, - UID_GenericImplantTemplateStorage, - UID_GrayscaleSoftcopyPresentationStateStorage, - UID_HemodynamicWaveformStorage, - UID_ImplantAssemblyTemplateStorage, - UID_ImplantationPlanSRDocumentStorage, - UID_ImplantTemplateGroupStorage, - UID_IntraocularLensCalculationsStorage, - UID_KeratometryMeasurementsStorage, - UID_KeyObjectSelectionDocumentStorage, - UID_LensometryMeasurementsStorage, - UID_MacularGridThicknessAndVolumeReportStorage, - UID_MammographyCADSRStorage, - UID_MRImageStorage, - UID_MRSpectroscopyStorage, - UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage, - UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage, - UID_MultiframeSingleBitSecondaryCaptureImageStorage, - UID_MultiframeTrueColorSecondaryCaptureImageStorage, - UID_NuclearMedicineImageStorage, - UID_OphthalmicAxialMeasurementsStorage, - UID_OphthalmicPhotography16BitImageStorage, - UID_OphthalmicPhotography8BitImageStorage, - UID_OphthalmicTomographyImageStorage, - UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage, - UID_PositronEmissionTomographyImageStorage, - UID_ProcedureLogStorage, - UID_PseudoColorSoftcopyPresentationStateStorage, - UID_RawDataStorage, - UID_RealWorldValueMappingStorage, - UID_RespiratoryWaveformStorage, - UID_RTBeamsTreatmentRecordStorage, - UID_RTBrachyTreatmentRecordStorage, - UID_RTDoseStorage, - UID_RTImageStorage, - UID_RTIonBeamsTreatmentRecordStorage, - UID_RTIonPlanStorage, - UID_RTPlanStorage, - UID_RTStructureSetStorage, - UID_RTTreatmentSummaryRecordStorage, - UID_SecondaryCaptureImageStorage, - UID_SegmentationStorage, - UID_SpatialFiducialsStorage, - UID_SpatialRegistrationStorage, - UID_SpectaclePrescriptionReportStorage, - UID_StereometricRelationshipStorage, - UID_SubjectiveRefractionMeasurementsStorage, - UID_SurfaceSegmentationStorage, - UID_TwelveLeadECGWaveformStorage, - UID_UltrasoundImageStorage, - UID_UltrasoundMultiframeImageStorage, - UID_VideoEndoscopicImageStorage, - UID_VideoMicroscopicImageStorage, - UID_VideoPhotographicImageStorage, - UID_VisualAcuityMeasurementsStorage, - UID_VLEndoscopicImageStorage, - UID_VLMicroscopicImageStorage, - UID_VLPhotographicImageStorage, - UID_VLSlideCoordinatesMicroscopicImageStorage, - UID_VLWholeSlideMicroscopyImageStorage, - UID_XAXRFGrayscaleSoftcopyPresentationStateStorage, - UID_XRay3DAngiographicImageStorage, - UID_XRay3DCraniofacialImageStorage, - UID_XRayAngiographicImageStorage, - UID_XRayRadiationDoseSRStorage, - UID_XRayRadiofluoroscopicImageStorage, - // retired - UID_RETIRED_HardcopyColorImageStorage, - UID_RETIRED_HardcopyGrayscaleImageStorage, - UID_RETIRED_NuclearMedicineImageStorage, - UID_RETIRED_StandaloneCurveStorage, - UID_RETIRED_StandaloneModalityLUTStorage, - UID_RETIRED_StandaloneOverlayStorage, - UID_RETIRED_StandalonePETCurveStorage, - UID_RETIRED_StandaloneVOILUTStorage, - UID_RETIRED_StoredPrintStorage, - UID_RETIRED_UltrasoundImageStorage, - UID_RETIRED_UltrasoundMultiframeImageStorage, - UID_RETIRED_VLImageStorage, - UID_RETIRED_VLMultiFrameImageStorage, - UID_RETIRED_XRayAngiographicBiPlaneImageStorage, - // draft - UID_DRAFT_SRAudioStorage, - UID_DRAFT_SRComprehensiveStorage, - UID_DRAFT_SRDetailStorage, - UID_DRAFT_SRTextStorage, - UID_DRAFT_WaveformStorage, - UID_DRAFT_RTBeamsDeliveryInstructionStorage, - NULL - }; - - const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1; - - - - OFCondition AssociationCleanup(T_ASC_Association *assoc) - { - OFString temp_str; - OFCondition cond = ASC_dropSCPAssociation(assoc); - if (cond.bad()) - { - LOG(FATAL) << cond.text(); - return cond; - } - - cond = ASC_destroyAssociation(&assoc); - if (cond.bad()) - { - LOG(FATAL) << cond.text(); - return cond; - } - - return cond; - } - - - - CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net) - { - DcmAssociationConfiguration asccfg; - char buf[BUFSIZ]; - T_ASC_Association *assoc; - OFCondition cond; - 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); - } - - // 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, - /*opt_secureConnection*/ OFFalse, - DUL_NOBLOCK, 1); - - if (cond == DUL_NOASSOCIATIONREQUEST) - { - // Timeout - AssociationCleanup(assoc); - return NULL; - } - - // if some kind of error occured, take care of it - if (cond.bad()) - { - LOG(ERROR) << "Receiving Association failed: " << cond.text(); - // no matter what kind of error occurred, we need to do a cleanup - AssociationCleanup(assoc); - return NULL; - } - - // Retrieve the AET and the IP address of the remote modality - std::string callingAet; - std::string callingIp; - std::string calledAet; - - { - DIC_AE callingAet_C; - DIC_AE calledAet_C; - DIC_AE callingIp_C; - DIC_AE calledIP_C; - if (ASC_getAPTitles(assoc->params, callingAet_C, calledAet_C, NULL).bad() || - ASC_getPresentationAddresses(assoc->params, callingIp_C, calledIP_C).bad()) - { - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_NOREASON - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - callingIp = std::string(/*OFSTRING_GUARD*/(callingIp_C)); - callingAet = std::string(/*OFSTRING_GUARD*/(callingAet_C)); - calledAet = (/*OFSTRING_GUARD*/(calledAet_C)); - } - - LOG(INFO) << "Association Received from AET " << callingAet - << " on IP " << callingIp; - - - 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(callingIp, callingAet, TransferSyntax_Deflated)) - { - transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, 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); - transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); - transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpeg2000)) - { - transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_JpegLossless)) - { - transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpip)) - { - transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); - transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Mpeg2)) - { - transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); - transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, 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; - } - - /* the array of Storage SOP Class UIDs comes from dcuid.h */ - cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &transferSyntaxes[0], transferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - -#if ORTHANC_PROMISCUOUS == 1 - /* accept everything not known not to be a storage SOP class */ - cond = acceptUnknownContextsWithPreferredTransferSyntaxes( - assoc->params, &transferSyntaxes[0], transferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } -#endif - - /* set our app title */ - ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); - - /* acknowledge or reject this association */ - cond = ASC_getApplicationContextName(assoc->params, buf); - if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0) - { - /* reject: the application context name is not supported */ - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED - }; - - LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf; - cond = ASC_rejectAssociation(assoc, &rej); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - } - AssociationCleanup(assoc); - return NULL; - } - - /* check the AETs */ - if (!server.IsMyAETitle(calledAet)) - { - LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")"; - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - if (server.HasApplicationEntityFilter() && - !server.GetApplicationEntityFilter().IsAllowedConnection(callingIp, callingAet)) - { - LOG(WARNING) << "Rejected association for remote AET " << callingAet << " on IP " << callingIp; - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - if (opt_rejectWithoutImplementationUID && - strlen(assoc->params->theirImplementationClassUID) == 0) - { - /* reject: the no implementation Class UID provided */ - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_NOREASON - }; - - LOG(INFO) << "Association Rejected: No Implementation Class UID provided"; - cond = ASC_rejectAssociation(assoc, &rej); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - } - AssociationCleanup(assoc); - return NULL; - } - - { - cond = ASC_acknowledgeAssociation(assoc); - if (cond.bad()) - { - LOG(ERROR) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"; - if (ASC_countAcceptedPresentationContexts(assoc->params) == 0) - LOG(INFO) << " (but no valid presentation contexts)"; - } - - IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; - return new CommandDispatcher(server, assoc, callingIp, callingAet, filter); - } - - bool CommandDispatcher::Step() - /* - * This function receives DIMSE commmands over the network connection - * and handles these commands correspondingly. Note that in case of - * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed. - */ - { - bool finished = false; - - // receive a DIMSE command over the network, with a timeout of 1 second - DcmDataset *statusDetail = NULL; - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message msg; - - OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail); - elapsedTimeSinceLastCommand_++; - - // if the command which was received has extra status - // detail information, dump this information - if (statusDetail != NULL) - { - //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail)); - delete statusDetail; - } - - if (cond == DIMSE_OUTOFRESOURCES) - { - finished = true; - } - else if (cond == DIMSE_NODATAAVAILABLE) - { - // Timeout due to DIMSE_NONBLOCKING - if (clientTimeout_ != 0 && - elapsedTimeSinceLastCommand_ >= clientTimeout_) - { - // This timeout is actually a client timeout - finished = true; - } - } - else if (cond == EC_Normal) - { - // Reset the client timeout counter - elapsedTimeSinceLastCommand_ = 0; - - // Convert the type of request to Orthanc's internal type - bool supported = false; - DicomRequestType request; - switch (msg.CommandField) - { - case DIMSE_C_ECHO_RQ: - request = DicomRequestType_Echo; - supported = true; - break; - - case DIMSE_C_STORE_RQ: - request = DicomRequestType_Store; - supported = true; - break; - - case DIMSE_C_MOVE_RQ: - request = DicomRequestType_Move; - supported = true; - break; - - case DIMSE_C_FIND_RQ: - request = DicomRequestType_Find; - supported = true; - break; - - default: - // we cannot handle this kind of message - cond = DIMSE_BADCOMMANDTYPE; - LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; - break; - } - - - // Check whether this request is allowed by the security filter - if (supported && - request != DicomRequestType_Echo && // Always allow incoming ECHO requests - filter_ != NULL && - !filter_->IsAllowedRequest(remoteIp_, remoteAet_, request)) - { - LOG(ERROR) << EnumerationToString(request) - << " requests are disallowed for the AET \"" - << remoteAet_ << "\""; - cond = DIMSE_ILLEGALASSOCIATION; - supported = false; - finished = true; - } - - // in case we received a supported message, process this command - if (supported) - { - // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer - cond = DIMSE_BADCOMMANDTYPE; - - switch (request) - { - case DicomRequestType_Echo: - cond = EchoScp(assoc_, &msg, presID); - break; - - case DicomRequestType_Store: - if (server_.HasStoreRequestHandlerFactory()) // Should always be true - { - std::auto_ptr<IStoreRequestHandler> handler - (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); - cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_); - } - break; - - case DicomRequestType_Move: - if (server_.HasMoveRequestHandlerFactory()) // Should always be true - { - std::auto_ptr<IMoveRequestHandler> handler - (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); - cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_); - } - break; - - case DicomRequestType_Find: - if (server_.HasFindRequestHandlerFactory()) // Should always be true - { - std::auto_ptr<IFindRequestHandler> handler - (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); - cond = Internals::findScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_); - } - break; - - default: - // Should never happen - break; - } - } - } - else - { - // Bad status, which indicates the closing of the connection by - // the peer or a network error - finished = true; - - LOG(INFO) << cond.text(); - } - - if (finished) - { - if (cond == DUL_PEERREQUESTEDRELEASE) - { - LOG(INFO) << "Association Release"; - ASC_acknowledgeRelease(assoc_); - } - else if (cond == DUL_PEERABORTEDASSOCIATION) - { - LOG(INFO) << "Association Aborted"; - } - else - { - OFString temp_str; - LOG(ERROR) << "DIMSE failure (aborting association): " << cond.text(); - /* some kind of error so abort the association */ - ASC_abortAssociation(assoc_); - } - } - - return !finished; - } - - - OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) - { - OFString temp_str; - LOG(INFO) << "Received Echo Request"; - //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID)); - - /* the echo succeeded !! */ - OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL); - if (cond.bad()) - { - LOG(ERROR) << "Echo SCP Failed: " << cond.text(); - } - return cond; - } - } -}
--- a/OrthancServer/Internals/CommandDispatcher.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/DicomServer.h" -#include "../../Core/MultiThreading/IRunnableBySteps.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition AssociationCleanup(T_ASC_Association *assoc); - - class CommandDispatcher : public IRunnableBySteps - { - private: - uint32_t clientTimeout_; - uint32_t elapsedTimeSinceLastCommand_; - const DicomServer& server_; - T_ASC_Association* assoc_; - std::string remoteIp_; - std::string remoteAet_; - IApplicationEntityFilter* filter_; - - public: - CommandDispatcher(const DicomServer& server, - T_ASC_Association* assoc, - const std::string& remoteIp, - const std::string& remoteAet, - IApplicationEntityFilter* filter) : - server_(server), - assoc_(assoc), - remoteIp_(remoteIp), - remoteAet_(remoteAet), - filter_(filter) - { - clientTimeout_ = server.GetClientTimeout(); - elapsedTimeSinceLastCommand_ = 0; - } - - virtual ~CommandDispatcher() - { - AssociationCleanup(assoc_); - } - - 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); - } -}
--- a/OrthancServer/Internals/DicomImageDecoder.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,698 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomImageDecoder.h" - - -/*========================================================================= - - This file is based on portions of the following project - (cf. function "DecodePsmctRle1()"): - - Program: GDCM (Grassroots DICOM). A DICOM library - Module: http://gdcm.sourceforge.net/Copyright.html - - Copyright (c) 2006-2011 Mathieu Malaterre - Copyright (c) 1993-2005 CREATIS - (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any - contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - =========================================================================*/ - - - -#include "../PrecompiledHeadersServer.h" -#include "DicomImageDecoder.h" - -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" -#include "../../Core/Images/ImageProcessing.h" -#include "../../Core/Images/PngWriter.h" // TODO REMOVE THIS -#include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" -#include "../ToDcmtkBridge.h" -#include "../FromDcmtkBridge.h" - -#include <boost/lexical_cast.hpp> - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 -#include <dcmtk/dcmjpls/djcodecd.h> -#include <dcmtk/dcmjpls/djcparam.h> -#include <dcmtk/dcmjpeg/djrplol.h> -#endif - - -namespace Orthanc -{ - static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); - static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); - - - static bool IsJpegLossless(const DcmDataset& dataset) - { - // http://support.dcmtk.org/docs/dcxfer_8h-source.html - return (dataset.getOriginalXfer() == EXS_JPEGLSLossless || - dataset.getOriginalXfer() == EXS_JPEGLSLossy); - } - - - static bool IsPsmctRle1(DcmDataset& dataset) - { - DcmElement* e; - char* c; - - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() || - e == NULL || - !e->isaString() || - !e->getString(c).good() || - c == NULL || - strcmp("PMSCT_RLE1", c)) - { - return false; - } - else - { - return true; - } - } - - - static bool DecodePsmctRle1(std::string& output, - DcmDataset& dataset) - { - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!IsPsmctRle1(dataset)) - { - return false; - } - - // OK, this is a custom RLE encoding from Philips. Get the pixel - // data from the appropriate private DICOM tag. - Uint8* pixData = NULL; - DcmElement* e; - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() || - e == NULL || - e->getUint8Array(pixData) != EC_Normal) - { - return false; - } - - // The "unsigned" below IS VERY IMPORTANT - const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData); - const size_t length = e->getLength(); - - /** - * The code below is an adaptation of a sample code for GDCM by - * Mathieu Malaterre (under a BSD license). - * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html - **/ - - // RLE pass - std::vector<uint8_t> temp; - temp.reserve(length); - for (size_t i = 0; i < length; i++) - { - if (inbuffer[i] == 0xa5) - { - temp.push_back(inbuffer[i+2]); - for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) - { - temp.push_back(inbuffer[i+2]); - } - i += 2; - } - else - { - temp.push_back(inbuffer[i]); - } - } - - // Delta encoding pass - uint16_t delta = 0; - output.clear(); - output.reserve(temp.size()); - for (size_t i = 0; i < temp.size(); i++) - { - uint16_t value; - - if (temp[i] == 0x5a) - { - uint16_t v1 = temp[i + 1]; - uint16_t v2 = temp[i + 2]; - value = (v2 << 8) + v1; - i += 2; - } - else - { - value = delta + (int8_t) temp[i]; - } - - output.push_back(value & 0xff); - output.push_back(value >> 8); - delta = value; - } - - if (output.size() % 2) - { - output.resize(output.size() - 1); - } - - return true; - } - - - class DicomImageDecoder::ImageSource - { - private: - std::string psmct_; - std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_; - - public: - void Setup(DcmDataset& dataset, - unsigned int frame) - { - psmct_.clear(); - slowAccessor_.reset(NULL); - - // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data - - DicomMap m; - FromDcmtkBridge::Convert(m, dataset); - - /** - * Create an accessor to the raw values of the DICOM image. - **/ - - DcmElement* e; - if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && - e != NULL) - { - Uint8* pixData = NULL; - if (e->getUint8Array(pixData) == EC_Normal) - { - slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); - } - } - else if (DecodePsmctRle1(psmct_, dataset)) - { - LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; - Uint8* pixData = NULL; - if (psmct_.size() > 0) - { - pixData = reinterpret_cast<Uint8*>(&psmct_[0]); - } - - slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size())); - } - - if (slowAccessor_.get() == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - slowAccessor_->SetCurrentFrame(frame); - } - - unsigned int GetWidth() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetWidth(); - } - - unsigned int GetHeight() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetHeight(); - } - - unsigned int GetChannelCount() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetChannelCount(); - } - - const DicomIntegerPixelAccessor& GetAccessor() const - { - assert(slowAccessor_.get() != NULL); - return *slowAccessor_; - } - - unsigned int GetSize() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetSize(); - } - }; - - - void DicomImageDecoder::SetupImageBuffer(ImageBuffer& target, - DcmDataset& dataset) - { - DicomMap m; - FromDcmtkBridge::Convert(m, dataset); - - DicomImageInformation info(m); - PixelFormat format; - - if (!info.ExtractPixelFormat(format)) - { - LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() - << "bpp, " << info.GetChannelCount() << " channels, " - << (info.IsSigned() ? "signed" : "unsigned") - << (info.IsPlanar() ? ", planar, " : ", non-planar, ") - << EnumerationToString(info.GetPhotometricInterpretation()) - << " photometric interpretation"; - throw OrthancException(ErrorCode_NotImplemented); - } - - target.SetHeight(info.GetHeight()); - target.SetWidth(info.GetWidth()); - target.SetFormat(format); - } - - - bool DicomImageDecoder::IsUncompressedImage(const DcmDataset& dataset) - { - // http://support.dcmtk.org/docs/dcxfer_8h-source.html - return (dataset.getOriginalXfer() == EXS_Unknown || - dataset.getOriginalXfer() == EXS_LittleEndianImplicit || - dataset.getOriginalXfer() == EXS_BigEndianImplicit || - dataset.getOriginalXfer() == EXS_LittleEndianExplicit || - dataset.getOriginalXfer() == EXS_BigEndianExplicit); - } - - - template <typename PixelType> - static void CopyPixels(ImageAccessor& target, - const DicomIntegerPixelAccessor& source) - { - const PixelType minValue = std::numeric_limits<PixelType>::min(); - const PixelType maxValue = std::numeric_limits<PixelType>::max(); - - for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++) - { - PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y)); - for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++) - { - for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++) - { - int32_t v = source.GetValue(x, y, c); - if (v < static_cast<int32_t>(minValue)) - { - *pixel = minValue; - } - else if (v > static_cast<int32_t>(maxValue)) - { - *pixel = maxValue; - } - else - { - *pixel = static_cast<PixelType>(v); - } - } - } - } - } - - - void DicomImageDecoder::DecodeUncompressedImage(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame) - { - if (!IsUncompressedImage(dataset)) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - DecodeUncompressedImageInternal(target, dataset, frame); - } - - - void DicomImageDecoder::DecodeUncompressedImageInternal(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame) - { - ImageSource source; - source.Setup(dataset, frame); - - - /** - * Resize the target image. - **/ - - SetupImageBuffer(target, dataset); - - if (source.GetWidth() != target.GetWidth() || - source.GetHeight() != target.GetHeight()) - { - throw OrthancException(ErrorCode_InternalError); - } - - - /** - * If the format of the DICOM buffer is natively supported, use a - * direct access to copy its values. - **/ - - ImageAccessor targetAccessor(target.GetAccessor()); - const DicomImageInformation& info = source.GetAccessor().GetInformation(); - - bool fastVersionSuccess = false; - PixelFormat sourceFormat; - if (!info.IsPlanar() && - info.ExtractPixelFormat(sourceFormat)) - { - try - { - size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat); - if ((frame + 1) * frameSize <= source.GetSize()) - { - const uint8_t* buffer = reinterpret_cast<const uint8_t*>(source.GetAccessor().GetPixelData()); - - ImageAccessor sourceImage; - sourceImage.AssignReadOnly(sourceFormat, - info.GetWidth(), - info.GetHeight(), - info.GetWidth() * GetBytesPerPixel(sourceFormat), - buffer + frame * frameSize); - - ImageProcessing::Convert(targetAccessor, sourceImage); - ImageProcessing::ShiftRight(targetAccessor, info.GetShift()); - fastVersionSuccess = true; - } - } - catch (OrthancException&) - { - // Unsupported conversion, use the slow version - } - } - - /** - * Slow version : loop over the DICOM buffer, storing its value - * into the target image. - **/ - - if (!fastVersionSuccess) - { - switch (target.GetFormat()) - { - case PixelFormat_RGB24: - case PixelFormat_RGBA32: - case PixelFormat_Grayscale8: - CopyPixels<uint8_t>(targetAccessor, source.GetAccessor()); - break; - - case PixelFormat_Grayscale16: - CopyPixels<uint16_t>(targetAccessor, source.GetAccessor()); - break; - - case PixelFormat_SignedGrayscale16: - CopyPixels<int16_t>(targetAccessor, source.GetAccessor()); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - } - - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 - void DicomImageDecoder::DecodeJpegLossless(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame) - { - if (!IsJpegLossless(dataset)) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - DcmElement *element = NULL; - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), element).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); - DcmPixelSequence* pixelSequence = NULL; - if (!pixelData.getEncapsulatedRepresentation - (dataset.getOriginalXfer(), NULL, pixelSequence).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - SetupImageBuffer(target, dataset); - - ImageAccessor targetAccessor(target.GetAccessor()); - - /** - * The "DJLSLosslessDecoder" and "DJLSNearLosslessDecoder" in DCMTK - * are exactly the same, except for the "supportedTransferSyntax()" - * virtual function. - * http://support.dcmtk.org/docs/classDJLSDecoderBase.html - **/ - - DJLSLosslessDecoder decoder; DJLSCodecParameter parameters; - //DJLSNearLosslessDecoder decoder; DJLSCodecParameter parameters; - - Uint32 startFragment = 0; // Default - OFString decompressedColorModel; // Out - DJ_RPLossless representationParameter; - OFCondition c = decoder.decodeFrame(&representationParameter, pixelSequence, ¶meters, - &dataset, frame, startFragment, targetAccessor.GetBuffer(), - targetAccessor.GetSize(), decompressedColorModel); - - if (!c.good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } -#endif - - - - - bool DicomImageDecoder::Decode(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame) - { - if (IsUncompressedImage(dataset)) - { - DecodeUncompressedImage(target, dataset, frame); - return true; - } - - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 - if (IsJpegLossless(dataset)) - { - LOG(INFO) << "Decoding a JPEG-LS image"; - DecodeJpegLossless(target, dataset, frame); - return true; - } -#endif - - -#if ORTHANC_JPEG_ENABLED == 1 - // TODO Implement this part to speed up JPEG decompression -#endif - - - /** - * This DICOM image format is not natively supported by - * Orthanc. As a last resort, try and decode it through - * DCMTK. This will result in higher memory consumption. This is - * actually the second example of the following page: - * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples - **/ - - { - LOG(INFO) << "Using DCMTK to decode a compressed image"; - - std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); - converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); - - if (converted->canWriteXfer(EXS_LittleEndianExplicit)) - { - DecodeUncompressedImageInternal(target, *converted, frame); - return true; - } - } - - return false; - } - - - static bool IsColorImage(PixelFormat format) - { - return (format == PixelFormat_RGB24 || - format == PixelFormat_RGBA32); - } - - - bool DicomImageDecoder::DecodeAndTruncate(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame, - PixelFormat format, - bool allowColorConversion) - { - // TODO Special case for uncompressed images - - ImageBuffer source; - if (!Decode(source, dataset, frame)) - { - return false; - } - - // If specified, prevent the conversion between color and - // grayscale images - bool isSourceColor = IsColorImage(source.GetFormat()); - bool isTargetColor = IsColorImage(format); - - if (!allowColorConversion) - { - if (isSourceColor ^ isTargetColor) - { - return false; - } - } - - if (source.GetFormat() == format) - { - // No conversion is required, return the temporary image - target.AcquireOwnership(source); - return true; - } - - target.SetFormat(format); - target.SetWidth(source.GetWidth()); - target.SetHeight(source.GetHeight()); - - ImageAccessor targetAccessor(target.GetAccessor()); - ImageAccessor sourceAccessor(source.GetAccessor()); - ImageProcessing::Convert(targetAccessor, sourceAccessor); - - return true; - } - - - bool DicomImageDecoder::DecodePreview(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame) - { - // TODO Special case for uncompressed images - - ImageBuffer source; - if (!Decode(source, dataset, frame)) - { - return false; - } - - switch (source.GetFormat()) - { - case PixelFormat_RGB24: - { - // Directly return color images (RGB) - target.AcquireOwnership(source); - return true; - } - - case PixelFormat_Grayscale8: - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - // Grayscale image: Stretch its dynamics to the [0,255] range - target.SetFormat(PixelFormat_Grayscale8); - target.SetWidth(source.GetWidth()); - target.SetHeight(source.GetHeight()); - - ImageAccessor targetAccessor(target.GetAccessor()); - ImageAccessor sourceAccessor(source.GetAccessor()); - - int64_t a, b; - ImageProcessing::GetMinMaxValue(a, b, sourceAccessor); - - if (a == b) - { - ImageProcessing::Set(targetAccessor, 0); - } - else - { - ImageProcessing::ShiftScale(sourceAccessor, static_cast<float>(-a), 255.0f / static_cast<float>(b - a)); - - if (source.GetFormat() == PixelFormat_Grayscale8) - { - target.AcquireOwnership(source); - } - else - { - ImageProcessing::Convert(targetAccessor, sourceAccessor); - } - } - - return true; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } -}
--- a/OrthancServer/Internals/DicomImageDecoder.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <dcmtk/dcmdata/dcfilefo.h> - -#include "../../Core/Images/ImageBuffer.h" - -namespace Orthanc -{ - class DicomImageDecoder - { - private: - class ImageSource; - - static void DecodeUncompressedImageInternal(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame); - - static bool IsPsmctRle1(DcmDataset& dataset); - - static void SetupImageBuffer(ImageBuffer& target, - DcmDataset& dataset); - - static bool IsUncompressedImage(const DcmDataset& dataset); - - static void DecodeUncompressedImage(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame); - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 - static void DecodeJpegLossless(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame); -#endif - - public: - static bool Decode(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame); - - static bool DecodeAndTruncate(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame, - PixelFormat format, - bool allowColorConversion); - - static bool DecodePreview(ImageBuffer& target, - DcmDataset& dataset, - unsigned int frame); - }; -}
--- a/OrthancServer/Internals/FindScp.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - - -#include "../PrecompiledHeadersServer.h" -#include "FindScp.h" - -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" - - - -namespace Orthanc -{ - namespace - { - struct FindScpData - { - IFindRequestHandler* handler_; - DicomMap input_; - DicomFindAnswers answers_; - DcmDataset* lastRequest_; - const std::string* remoteIp_; - const std::string* remoteAet_; - bool noCroppingOfResults_; - }; - - - void FindScpCallback( - /* in */ - void *callbackData, - OFBool cancelled, - T_DIMSE_C_FindRQ *request, - DcmDataset *requestIdentifiers, - int responseCount, - /* out */ - T_DIMSE_C_FindRSP *response, - DcmDataset **responseIdentifiers, - DcmDataset **statusDetail) - { - bzero(response, sizeof(T_DIMSE_C_FindRSP)); - *statusDetail = NULL; - - FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData); - if (data.lastRequest_ == NULL) - { - FromDcmtkBridge::Convert(data.input_, *requestIdentifiers); - - try - { - data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, - *data.remoteIp_, *data.remoteAet_); - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "C-FIND request handler has failed: " << e.What(); - response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; - *responseIdentifiers = NULL; - return; - } - - data.lastRequest_ = requestIdentifiers; - } - else if (data.lastRequest_ != requestIdentifiers) - { - // Internal error! - response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; - *responseIdentifiers = NULL; - return; - } - - if (responseCount <= static_cast<int>(data.answers_.GetSize())) - { - // There are pending results that are still to be sent - response->DimseStatus = STATUS_Pending; - *responseIdentifiers = ToDcmtkBridge::Convert(data.answers_.GetAnswer(responseCount - 1)); - } - else if (data.noCroppingOfResults_) - { - // Success: All the results have been sent - response->DimseStatus = STATUS_Success; - *responseIdentifiers = NULL; - } - else - { - // Success, but the results were too numerous and had to be cropped - LOG(WARNING) << "Too many results for an incoming C-FIND query"; - response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest; - *responseIdentifiers = NULL; - } - } - } - - - OFCondition Internals::findScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IFindRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet) - { - FindScpData data; - data.lastRequest_ = NULL; - data.handler_ = &handler; - data.remoteIp_ = &remoteIp; - data.remoteAet_ = &remoteAet; - data.noCroppingOfResults_ = true; - - OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, - FindScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Find SCP Failed: " << cond.text(); - } - - return cond; - } -}
--- a/OrthancServer/Internals/FindScp.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/IFindRequestHandler.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition findScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IFindRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet); - } -}
--- a/OrthancServer/Internals/MoveScp.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,241 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "MoveScp.h" - -#include <memory> - -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" - - -namespace Orthanc -{ - namespace - { - struct MoveScpData - { - std::string target_; - IMoveRequestHandler* handler_; - DicomMap input_; - DcmDataset* lastRequest_; - unsigned int subOperationCount_; - unsigned int failureCount_; - unsigned int warningCount_; - std::auto_ptr<IMoveRequestIterator> iterator_; - const std::string* remoteIp_; - const std::string* remoteAet_; - }; - - - void MoveScpCallback( - /* in */ - void *callbackData, - OFBool cancelled, - T_DIMSE_C_MoveRQ *request, - DcmDataset *requestIdentifiers, - int responseCount, - /* out */ - T_DIMSE_C_MoveRSP *response, - DcmDataset **responseIdentifiers, - DcmDataset **statusDetail) - { - bzero(response, sizeof(T_DIMSE_C_MoveRSP)); - *statusDetail = NULL; - *responseIdentifiers = NULL; - - MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData); - if (data.lastRequest_ == NULL) - { - FromDcmtkBridge::Convert(data.input_, *requestIdentifiers); - - try - { - data.iterator_.reset(data.handler_->Handle(data.target_, data.input_, - *data.remoteIp_, *data.remoteAet_)); - - if (data.iterator_.get() == NULL) - { - // Internal error! - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - data.subOperationCount_ = data.iterator_->GetSubOperationCount(); - data.failureCount_ = 0; - data.warningCount_ = 0; - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - data.lastRequest_ = requestIdentifiers; - } - else if (data.lastRequest_ != requestIdentifiers) - { - // Internal error! - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - if (data.subOperationCount_ == 0) - { - response->DimseStatus = STATUS_Success; - } - else - { - IMoveRequestIterator::Status status; - - try - { - status = data.iterator_->DoNext(); - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - if (status == IMoveRequestIterator::Status_Failure) - { - data.failureCount_++; - } - else if (status == IMoveRequestIterator::Status_Warning) - { - data.warningCount_++; - } - - if (responseCount < static_cast<int>(data.subOperationCount_)) - { - response->DimseStatus = STATUS_Pending; - } - else - { - response->DimseStatus = STATUS_Success; - } - } - - response->NumberOfRemainingSubOperations = data.subOperationCount_ - responseCount; - response->NumberOfCompletedSubOperations = responseCount; - response->NumberOfFailedSubOperations = data.failureCount_; - response->NumberOfWarningSubOperations = data.warningCount_; - } - } - - - OFCondition Internals::moveScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet) - { - MoveScpData data; - data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination); - data.lastRequest_ = NULL; - data.handler_ = &handler; - data.remoteIp_ = &remoteIp; - data.remoteAet_ = &remoteAet; - - OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, - MoveScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Move SCP Failed: " << cond.text(); - } - - return cond; - } -}
--- a/OrthancServer/Internals/MoveScp.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/IMoveRequestHandler.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition moveScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet); - } -}
--- a/OrthancServer/Internals/StoreScp.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,301 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "StoreScp.h" - -#include "../FromDcmtkBridge.h" -#include "../ServerToolbox.h" -#include "../ToDcmtkBridge.h" -#include "../../Core/OrthancException.h" -#include "../../Core/Logging.h" - -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcostrmb.h> -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmnet/diutil.h> - - -namespace Orthanc -{ - namespace - { - struct StoreCallbackData - { - IStoreRequestHandler* handler; - const std::string* remoteIp; - const char* remoteAET; - const char* calledAET; - const char* modality; - const char* affectedSOPInstanceUID; - uint32_t messageID; - }; - - - static void - storeScpCallback( - void *callbackData, - T_DIMSE_StoreProgress *progress, - T_DIMSE_C_StoreRQ *req, - char * /*imageFileName*/, DcmDataset **imageDataSet, - T_DIMSE_C_StoreRSP *rsp, - DcmDataset **statusDetail) - /* - * This function.is used to indicate progress when storescp receives instance data over the - * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd) - * this function will store the data set which was received over the network to a file. - * Earlier calls to this function will simply cause some information to be dumped to stdout. - * - * Parameters: - * callbackData - [in] data for this callback function - * progress - [in] The state of progress. (identifies if this is the initial or final call - * to this function, or a call in between these two calls. - * req - [in] The original store request message. - * imageFileName - [in] The path to and name of the file the information shall be written to. - * imageDataSet - [in] The data set which shall be stored in the image file - * rsp - [inout] the C-STORE-RSP message (will be sent after the call to this function) - * statusDetail - [inout] This variable can be used to capture detailed information with regard to - * the status information which is captured in the status element (0000,0900). Note - * that this function does specify any such information, the pointer will be set to NULL. - */ - { - StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData); - - DIC_UI sopClass; - DIC_UI sopInstance; - - // if this is the final call of this function, save the data which was received to a file - // (note that we could also save the image somewhere else, put it in database, etc.) - if (progress->state == DIMSE_StoreEnd) - { - OFString tmpStr; - - // do not send status detail information - *statusDetail = NULL; - - // Concerning the following line: an appropriate status code is already set in the resp structure, - // it need not be success. For example, if the caller has already detected an out of resources problem - // then the status will reflect this. The callback function is still called to allow cleanup. - //rsp->DimseStatus = STATUS_Success; - - // we want to write the received information to a file only if this information - // is present and the options opt_bitPreserving and opt_ignore are not set. - if ((imageDataSet != NULL) && (*imageDataSet != NULL)) - { - DicomMap summary; - Json::Value dicomJson; - std::string buffer; - - try - { - FromDcmtkBridge::Convert(summary, **imageDataSet); - FromDcmtkBridge::ToJson(dicomJson, **imageDataSet, - DicomToJsonFormat_Full, - DicomToJsonFlags_Default, - 256 /* max string length */); - - if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) - { - LOG(ERROR) << "cannot write DICOM file to memory"; - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - } - } - catch (...) - { - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - } - - // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond - // to those mentioned in the request. If not, set the status in the response message variable. - if (rsp->DimseStatus == STATUS_Success) - { - // which SOP class and SOP instance ? - if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse)) - { - //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName); - rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand; - } - else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0) - { - rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; - } - else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0) - { - rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; - } - else - { - try - { - cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); - } - catch (OrthancException& e) - { - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - - if (e.GetErrorCode() == ErrorCode_InexistentTag) - { - Toolbox::LogMissingRequiredTag(summary); - } - else - { - LOG(ERROR) << "Exception while storing DICOM: " << e.What(); - } - } - } - } - } - } - } - } - -/* - * This function processes a DIMSE C-STORE-RQ commmand that was - * received over the network connection. - * - * Parameters: - * assoc - [in] The association (network connection to another DICOM application). - * msg - [in] The DIMSE C-STORE-RQ message that was received. - * presID - [in] The ID of the presentation context which was specified in the PDV which contained - * the DIMSE command. - */ - OFCondition Internals::storeScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler, - const std::string& remoteIp) - { - OFCondition cond = EC_Normal; - T_DIMSE_C_StoreRQ *req; - - // assign the actual information of the C-STORE-RQ command to a local variable - req = &msg->msg.CStoreRQ; - - // intialize some variables - StoreCallbackData data; - data.handler = &handler; - data.remoteIp = &remoteIp; - data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/); - if (data.modality == NULL) - data.modality = "UNKNOWN"; - - data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID; - data.messageID = req->MessageID; - if (assoc && assoc->params) - { - data.remoteAET = assoc->params->DULparams.callingAPTitle; - data.calledAET = assoc->params->DULparams.calledAPTitle; - } - else - { - data.remoteAET = ""; - data.calledAET = ""; - } - - DcmFileFormat dcmff; - - // store SourceApplicationEntityTitle in metaheader - if (assoc && assoc->params) - { - const char *aet = assoc->params->DULparams.callingAPTitle; - if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet); - } - - // define an address where the information which will be received over the network will be stored - DcmDataset *dset = dcmff.getDataset(); - - cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset, - storeScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Store SCP Failed: " << cond.text(); - } - - // return return value - return cond; - } -}
--- a/OrthancServer/Internals/StoreScp.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/IStoreRequestHandler.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition storeScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler, - const std::string& remoteIp); - } -}
--- a/OrthancServer/LuaScripting.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/LuaScripting.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,24 +34,217 @@ #include "PrecompiledHeadersServer.h" #include "LuaScripting.h" +#include "OrthancInitialization.h" +#include "OrthancRestApi/OrthancRestApi.h" #include "ServerContext.h" -#include "OrthancInitialization.h" -#include "../Core/Lua/LuaFunctionCall.h" + #include "../Core/HttpServer/StringHttpOutput.h" #include "../Core/Logging.h" - -#include "Scheduler/DeleteInstanceCommand.h" -#include "Scheduler/StoreScuCommand.h" -#include "Scheduler/StorePeerCommand.h" -#include "Scheduler/ModifyInstanceCommand.h" -#include "Scheduler/CallSystemCommand.h" -#include "OrthancRestApi/OrthancRestApi.h" +#include "../Core/Lua/LuaFunctionCall.h" #include <EmbeddedResources.h> namespace Orthanc { + class LuaScripting::IEvent : public IDynamicObject + { + public: + virtual void Apply(LuaScripting& lock) = 0; + }; + + + class LuaScripting::OnStoredInstanceEvent : public LuaScripting::IEvent + { + private: + std::string instanceId_; + Json::Value simplifiedTags_; + Json::Value metadata_; + Json::Value origin_; + + public: + OnStoredInstanceEvent(const std::string& instanceId, + const Json::Value& simplifiedTags, + const Json::Value& metadata, + const DicomInstanceToStore& instance) : + instanceId_(instanceId), + simplifiedTags_(simplifiedTags), + metadata_(metadata) + { + instance.GetOrigin().Format(origin_); + } + + virtual void Apply(LuaScripting& that) + { + static const char* NAME = "OnStoredInstance"; + + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(NAME)) + { + that.InitializeJob(); + + LuaFunctionCall call(lock.GetLua(), NAME); + call.PushString(instanceId_); + call.PushJson(simplifiedTags_); + call.PushJson(metadata_); + call.PushJson(origin_); + call.Execute(); + + that.SubmitJob(); + } + } + }; + + + class LuaScripting::ExecuteEvent : public LuaScripting::IEvent + { + private: + std::string command_; + + public: + ExecuteEvent(const std::string& command) : + command_(command) + { + } + + virtual void Apply(LuaScripting& that) + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(command_.c_str())) + { + LuaFunctionCall call(lock.GetLua(), command_.c_str()); + call.Execute(); + } + } + }; + + + class LuaScripting::StableResourceEvent : public LuaScripting::IEvent + { + private: + ServerIndexChange change_; + + public: + StableResourceEvent(const ServerIndexChange& change) : + change_(change) + { + } + + virtual void Apply(LuaScripting& that) + { + const char* name; + + switch (change_.GetChangeType()) + { + case ChangeType_StablePatient: + name = "OnStablePatient"; + break; + + case ChangeType_StableStudy: + name = "OnStableStudy"; + break; + + case ChangeType_StableSeries: + name = "OnStableSeries"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + { + // Avoid unnecessary calls to the database if there's no Lua callback + LuaScripting::Lock lock(that); + + if (!lock.GetLua().IsExistingFunction(name)) + { + return; + } + } + + Json::Value tags, metadata; + if (that.context_.GetIndex().LookupResource(tags, change_.GetPublicId(), change_.GetResourceType()) && + that.context_.GetIndex().GetMetadata(metadata, change_.GetPublicId())) + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(name)) + { + that.InitializeJob(); + + LuaFunctionCall call(lock.GetLua(), name); + call.PushString(change_.GetPublicId()); + call.PushJson(tags["MainDicomTags"]); + call.PushJson(metadata); + call.Execute(); + + that.SubmitJob(); + } + } + } + }; + + + class LuaScripting::JobEvent : public LuaScripting::IEvent + { + public: + enum Type + { + Type_Failure, + Type_Submitted, + Type_Success + }; + + private: + Type type_; + std::string jobId_; + + public: + JobEvent(Type type, + const std::string& jobId) : + type_(type), + jobId_(jobId) + { + } + + virtual void Apply(LuaScripting& that) + { + std::string functionName; + + switch (type_) + { + case Type_Failure: + functionName = "OnJobFailure"; + break; + + case Type_Submitted: + functionName = "OnJobSubmitted"; + break; + + case Type_Success: + functionName = "OnJobSuccess"; + 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(jobId_); + call.Execute(); + } + } + } + }; + + ServerContext* LuaScripting::GetServerContext(lua_State *state) { const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext"); @@ -210,7 +404,7 @@ } LOG(ERROR) << "Lua: Error in RestApiDelete() for URI: " << uri; - lua_pushnil(state); + lua_pushnil(state); return 1; } @@ -228,13 +422,14 @@ } - IServerCommand* LuaScripting::ParseOperation(const std::string& operation, - const Json::Value& parameters) + size_t LuaScripting::ParseOperation(LuaJobManager::Lock& lock, + const std::string& operation, + const Json::Value& parameters) { if (operation == "delete") { LOG(INFO) << "Lua script to delete resource " << parameters["Resource"].asString(); - return new DeleteInstanceCommand(context_); + return lock.AddDeleteResourceOperation(context_); } if (operation == "store-scu") @@ -249,34 +444,35 @@ localAet = context_.GetDefaultLocalApplicationEntityTitle(); } - std::string modality = parameters["Modality"].asString(); - LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString() - << " to modality " << modality << " using Store-SCU"; - return new StoreScuCommand(context_, localAet, - Configuration::GetModalityUsingSymbolicName(modality), true); + std::string name = parameters["Modality"].asString(); + RemoteModalityParameters modality = Configuration::GetModalityUsingSymbolicName(name); + + // This is not a C-MOVE: No need to call "StoreScuCommand::SetMoveOriginator()" + return lock.AddStoreScuOperation(localAet, modality); } if (operation == "store-peer") { - std::string peer = parameters["Peer"].asString(); - LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString() - << " to peer " << peer << " using HTTP"; + std::string name = parameters["Peer"].asString(); - OrthancPeerParameters parameters; - Configuration::GetOrthancPeer(parameters, peer); - return new StorePeerCommand(context_, parameters, true); + WebServiceParameters peer; + if (Configuration::GetOrthancPeer(peer, name)) + { + return lock.AddStorePeerOperation(peer); + } + else + { + LOG(ERROR) << "No peer with symbolic name: " << name; + throw OrthancException(ErrorCode_UnknownResource); + } } if (operation == "modify") { - LOG(INFO) << "Lua script to modify resource " << parameters["Resource"].asString(); std::auto_ptr<DicomModification> modification(new DicomModification); - OrthancRestApi::ParseModifyRequest(*modification, parameters); + modification->ParseModifyRequest(parameters); - std::auto_ptr<ModifyInstanceCommand> command - (new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification.release())); - - return command.release(); + return lock.AddModifyInstanceOperation(context_, modification.release()); } if (operation == "call-system") @@ -317,7 +513,10 @@ } } - return new CallSystemCommand(context_, parameters["Command"].asString(), args); + std::string command = parameters["Command"].asString(); + std::vector<std::string> postArgs; + + return lock.AddSystemCallOperation(command, args, postArgs); } throw OrthancException(ErrorCode_ParameterOutOfRange); @@ -330,7 +529,7 @@ } - void LuaScripting::SubmitJob(const std::string& description) + void LuaScripting::SubmitJob() { Json::Value operations; LuaFunctionCall call2(lua_, "_AccessJob"); @@ -341,8 +540,10 @@ throw OrthancException(ErrorCode_InternalError); } - ServerJob job; - ServerCommandInstance* previousCommand = NULL; + LuaJobManager::Lock lock(jobManager_, context_.GetJobsEngine()); + + bool isFirst = true; + size_t previous = 0; // Dummy initialization to avoid warning for (Json::Value::ArrayIndex i = 0; i < operations.size(); ++i) { @@ -353,34 +554,33 @@ } const Json::Value& parameters = operations[i]; - std::string operation = parameters["Operation"].asString(); - - ServerCommandInstance& command = job.AddCommand(ParseOperation(operation, operations[i])); - if (!parameters.isMember("Resource")) { throw OrthancException(ErrorCode_InternalError); } + std::string operation = parameters["Operation"].asString(); + size_t index = ParseOperation(lock, operation, operations[i]); + std::string resource = parameters["Resource"].asString(); - if (resource.empty()) + if (!resource.empty()) { - previousCommand->ConnectOutput(command); + lock.AddDicomInstanceInput(index, context_, resource); } - else + else if (!isFirst) { - command.AddInput(resource); + lock.Connect(previous, index); } - previousCommand = &command; + isFirst = false; + previous = index; } - - job.SetDescription(description); - context_.GetScheduler().Submit(job); } - LuaScripting::LuaScripting(ServerContext& context) : context_(context) + LuaScripting::LuaScripting(ServerContext& context) : + context_(context), + state_(State_Setup) { lua_.SetGlobalVariable("_ServerContext", &context); lua_.RegisterFunction("RestApiGet", RestApiGet); @@ -389,34 +589,90 @@ lua_.RegisterFunction("RestApiDelete", RestApiDelete); lua_.RegisterFunction("GetOrthancConfiguration", GetOrthancConfiguration); - lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); - lua_.SetHttpProxy(Configuration::GetGlobalStringParameter("HttpProxy", "")); + LOG(INFO) << "Initializing Lua for the event handler"; + LoadGlobalConfiguration(); + } + + + LuaScripting::~LuaScripting() + { + if (state_ == State_Running) + { + LOG(ERROR) << "INTERNAL ERROR: LuaScripting::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } } - void LuaScripting::ApplyOnStoredInstance(const std::string& instanceId, - const Json::Value& simplifiedTags, - const Json::Value& metadata, - const DicomInstanceToStore& instance) + void LuaScripting::EventThread(LuaScripting* that) { - static const char* NAME = "OnStoredInstance"; + for (;;) + { + std::auto_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100)); + + if (event.get() == NULL) + { + // The event queue is empty, check whether we should stop + boost::recursive_mutex::scoped_lock lock(that->mutex_); - if (lua_.IsExistingFunction(NAME)) - { - InitializeJob(); + if (that->state_ != State_Running) + { + return; + } + } + else + { + try + { + dynamic_cast<IEvent&>(*event).Apply(*that); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error while processing Lua events: " << e.What(); + } + } + } + } + + + void LuaScripting::Start() + { + boost::recursive_mutex::scoped_lock lock(mutex_); - LuaFunctionCall call(lua_, NAME); - call.PushString(instanceId); - call.PushJson(simplifiedTags); - call.PushJson(metadata); + if (state_ != State_Setup || + eventThread_.joinable() /* already started */) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + LOG(INFO) << "Starting the Lua engine"; + eventThread_ = boost::thread(EventThread, this); + state_ = State_Running; + } + } + - Json::Value origin; - instance.GetOriginInformation(origin); - call.PushJson(origin); + void LuaScripting::Stop() + { + { + boost::recursive_mutex::scoped_lock lock(mutex_); + + if (state_ != State_Running) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } - call.Execute(); + state_ = State_Done; + } + + jobManager_.AwakeTrailingSleep(); - SubmitJob(std::string("Lua script: ") + NAME); + if (eventThread_.joinable()) + { + LOG(INFO) << "Stopping the Lua engine"; + eventThread_.join(); + LOG(INFO) << "The Lua engine has stopped"; } } @@ -425,8 +681,6 @@ DicomInstanceToStore& instance, const Json::Value& simplifiedTags) { - boost::recursive_mutex::scoped_lock lock(mutex_); - Json::Value metadata = Json::objectValue; for (ServerIndex::MetadataMap::const_iterator @@ -439,64 +693,17 @@ } } - ApplyOnStoredInstance(publicId, simplifiedTags, metadata, instance); + pendingEvents_.Enqueue(new OnStoredInstanceEvent(publicId, simplifiedTags, metadata, instance)); } - - void LuaScripting::OnStableResource(const ServerIndexChange& change) - { - const char* name; - - switch (change.GetChangeType()) - { - case ChangeType_StablePatient: - name = "OnStablePatient"; - break; - - case ChangeType_StableStudy: - name = "OnStableStudy"; - break; - - case ChangeType_StableSeries: - name = "OnStableSeries"; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - - Json::Value tags, metadata; - if (context_.GetIndex().LookupResource(tags, change.GetPublicId(), change.GetResourceType()) && - context_.GetIndex().GetMetadata(metadata, change.GetPublicId())) - { - boost::recursive_mutex::scoped_lock lock(mutex_); - - if (lua_.IsExistingFunction(name)) - { - InitializeJob(); - - LuaFunctionCall call(lua_, name); - call.PushString(change.GetPublicId()); - call.PushJson(tags["MainDicomTags"]); - call.PushJson(metadata); - call.Execute(); - - SubmitJob(std::string("Lua script: ") + name); - } - } - } - - - void LuaScripting::SignalChange(const ServerIndexChange& change) { if (change.GetChangeType() == ChangeType_StablePatient || change.GetChangeType() == ChangeType_StableStudy || change.GetChangeType() == ChangeType_StableSeries) { - OnStableResource(change); + pendingEvents_.Enqueue(new StableResourceEvent(change)); } } @@ -514,7 +721,7 @@ call.PushJson(simplified); Json::Value origin; - instance.GetOriginInformation(origin); + instance.GetOrigin().Format(origin); call.PushJson(origin); if (!call.ExecutePredicate()) @@ -529,12 +736,46 @@ void LuaScripting::Execute(const std::string& command) { - LuaScripting::Locker locker(*this); - - if (locker.GetLua().IsExistingFunction(command.c_str())) + pendingEvents_.Enqueue(new ExecuteEvent(command)); + } + + + void LuaScripting::LoadGlobalConfiguration() + { + lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + + std::list<std::string> luaScripts; + Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); + + LuaScripting::Lock lock(*this); + + for (std::list<std::string>::const_iterator + it = luaScripts.begin(); it != luaScripts.end(); ++it) { - LuaFunctionCall call(locker.GetLua(), command.c_str()); - call.Execute(); + std::string path = Configuration::InterpretStringParameterAsPath(*it); + LOG(INFO) << "Installing the Lua scripts from: " << path; + std::string script; + SystemToolbox::ReadFile(script, path); + + lock.GetLua().Execute(script); } } + + + void LuaScripting::SignalJobSubmitted(const std::string& jobId) + { + pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Submitted, jobId)); + } + + + void LuaScripting::SignalJobSuccess(const std::string& jobId) + { + pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Success, jobId)); + } + + + void LuaScripting::SignalJobFailure(const std::string& jobId) + { + pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Failure, jobId)); + } }
--- a/OrthancServer/LuaScripting.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/LuaScripting.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,17 +33,32 @@ #pragma once -#include "IServerListener.h" +#include "DicomInstanceToStore.h" +#include "ServerJobs/LuaJobManager.h" + +#include "../Core/MultiThreading/SharedMessageQueue.h" #include "../Core/Lua/LuaContext.h" -#include "Scheduler/IServerCommand.h" namespace Orthanc { class ServerContext; - class LuaScripting : public IServerListener + class LuaScripting : public boost::noncopyable { private: + enum State + { + State_Setup, + State_Running, + State_Done + }; + + class ExecuteEvent; + class IEvent; + class OnStoredInstanceEvent; + class StableResourceEvent; + class JobEvent; + static ServerContext* GetServerContext(lua_State *state); static int RestApiPostOrPut(lua_State *state, @@ -53,33 +69,35 @@ static int RestApiDelete(lua_State *state); static int GetOrthancConfiguration(lua_State *state); - void ApplyOnStoredInstance(const std::string& instanceId, - const Json::Value& simplifiedDicom, - const Json::Value& metadata, - const DicomInstanceToStore& instance); - - IServerCommand* ParseOperation(const std::string& operation, - const Json::Value& parameters); + size_t ParseOperation(LuaJobManager::Lock& lock, + const std::string& operation, + const Json::Value& parameters); void InitializeJob(); - void SubmitJob(const std::string& description); - - void OnStableResource(const ServerIndexChange& change); + void SubmitJob(); - boost::recursive_mutex mutex_; - LuaContext lua_; - ServerContext& context_; + boost::recursive_mutex mutex_; + LuaContext lua_; + ServerContext& context_; + LuaJobManager jobManager_; + State state_; + boost::thread eventThread_; + SharedMessageQueue pendingEvents_; + + static void EventThread(LuaScripting* that); + + void LoadGlobalConfiguration(); public: - class Locker : public boost::noncopyable + class Lock : public boost::noncopyable { private: - LuaScripting& that_; - boost::recursive_mutex::scoped_lock lock_; + LuaScripting& that_; + boost::recursive_mutex::scoped_lock lock_; public: - Locker(LuaScripting& that) : + explicit Lock(LuaScripting& that) : that_(that), lock_(that.mutex_) { @@ -92,16 +110,28 @@ }; LuaScripting(ServerContext& context); - - virtual void SignalStoredInstance(const std::string& publicId, - DicomInstanceToStore& instance, - const Json::Value& simplifiedTags); + + ~LuaScripting(); + + void Start(); - virtual void SignalChange(const ServerIndexChange& change); + void Stop(); + + void SignalStoredInstance(const std::string& publicId, + DicomInstanceToStore& instance, + const Json::Value& simplifiedTags); - virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, - const Json::Value& simplifiedTags); + void SignalChange(const ServerIndexChange& change); + + bool FilterIncomingInstance(const DicomInstanceToStore& instance, + const Json::Value& simplifiedTags); void Execute(const std::string& command); + + void SignalJobSubmitted(const std::string& jobId); + + void SignalJobSuccess(const std::string& jobId); + + void SignalJobFailure(const std::string& jobId); }; }
--- a/OrthancServer/OrthancFindRequestHandler.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,9 @@ #include "OrthancFindRequestHandler.h" #include "../Core/DicomFormat/DicomArray.h" +#include "../Core/Lua/LuaFunctionCall.h" #include "../Core/Logging.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "OrthancInitialization.h" #include "Search/LookupResource.h" #include "ServerToolbox.h" @@ -45,21 +47,336 @@ namespace Orthanc { + static void GetChildren(std::list<std::string>& target, + ServerIndex& index, + const std::list<std::string>& source) + { + target.clear(); + + for (std::list<std::string>::const_iterator + it = source.begin(); it != source.end(); ++it) + { + std::list<std::string> tmp; + index.GetChildren(tmp, *it); + target.splice(target.end(), tmp); + } + } + + + static void StoreSetOfStrings(DicomMap& result, + const DicomTag& tag, + const std::set<std::string>& values) + { + bool isFirst = true; + + std::string s; + for (std::set<std::string>::const_iterator + it = values.begin(); it != values.end(); ++it) + { + if (isFirst) + { + isFirst = false; + } + else + { + s += "\\"; + } + + s += *it; + } + + result.SetValue(tag, s, false); + } + + + static void ExtractTagFromMainDicomTags(std::set<std::string>& target, + ServerIndex& index, + const DicomTag& tag, + const std::list<std::string>& resources, + ResourceType level) + { + for (std::list<std::string>::const_iterator + it = resources.begin(); it != resources.end(); ++it) + { + DicomMap tags; + if (index.GetMainDicomTags(tags, *it, level, level) && + tags.HasTag(tag)) + { + target.insert(tags.GetValue(tag).GetContent()); + } + } + } + + + static bool ExtractMetadata(std::set<std::string>& target, + ServerIndex& index, + MetadataType metadata, + const std::list<std::string>& resources) + { + for (std::list<std::string>::const_iterator + it = resources.begin(); it != resources.end(); ++it) + { + std::string value; + if (index.LookupMetadata(value, *it, metadata)) + { + target.insert(value); + } + else + { + // This metadata is unavailable for some resource, give up + return false; + } + } + + return true; + } + + + static void ExtractTagFromInstancesOnDisk(std::set<std::string>& target, + ServerContext& context, + const DicomTag& tag, + const std::list<std::string>& instances) + { + // WARNING: This function is slow, as it reads the JSON file + // summarizing each instance of interest from the hard drive. + + std::string formatted = tag.Format(); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + Json::Value dicom; + context.ReadDicomAsJson(dicom, *it); + + if (dicom.isMember(formatted)) + { + const Json::Value& source = dicom[formatted]; + + if (source.type() == Json::objectValue && + source.isMember("Type") && + source.isMember("Value") && + source["Type"].asString() == "String" && + source["Value"].type() == Json::stringValue) + { + target.insert(source["Value"].asString()); + } + } + } + } + + + static void ComputePatientCounters(DicomMap& result, + ServerIndex& index, + const std::string& patient, + const DicomMap& query) + { + std::list<std::string> studies; + index.GetChildren(studies, patient); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES, + boost::lexical_cast<std::string>(studies.size()), false); + } + + if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + return; + } + + std::list<std::string> series; + GetChildren(series, index, studies); + studies.clear(); // This information is useless below + + if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES, + boost::lexical_cast<std::string>(series.size()), false); + } + + if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + return; + } + + std::list<std::string> instances; + GetChildren(instances, index, series); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES, + boost::lexical_cast<std::string>(instances.size()), false); + } + } + + + static void ComputeStudyCounters(DicomMap& result, + ServerContext& context, + const std::string& study, + const DicomMap& query) + { + ServerIndex& index = context.GetIndex(); + + std::list<std::string> series; + index.GetChildren(series, study); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, + boost::lexical_cast<std::string>(series.size()), false); + } + + if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) + { + std::set<std::string> values; + ExtractTagFromMainDicomTags(values, index, DICOM_TAG_MODALITY, series, ResourceType_Series); + StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values); + } + + if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && + !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) + { + return; + } + + std::list<std::string> instances; + GetChildren(instances, index, series); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, + boost::lexical_cast<std::string>(instances.size()), false); + } + + if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) + { + std::set<std::string> values; + + if (ExtractMetadata(values, index, MetadataType_Instance_SopClassUid, instances)) + { + // The metadata "SopClassUid" is available for each of these instances + StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); + } + else if (Configuration::GetGlobalBoolParameter("AllowFindSopClassesInStudy", false)) + { + ExtractTagFromInstancesOnDisk(values, context, DICOM_TAG_SOP_CLASS_UID, instances); + StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); + } + else + { + result.SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, "", false); + LOG(WARNING) << "The handling of \"SOP Classes in Study\" (0008,0062) " + << "in C-FIND requests is disabled"; + } + } + } + + + static void ComputeSeriesCounters(DicomMap& result, + ServerIndex& index, + const std::string& series, + const DicomMap& query) + { + std::list<std::string> instances; + index.GetChildren(instances, series); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, + boost::lexical_cast<std::string>(instances.size()), false); + } + } + + + static DicomMap* ComputeCounters(ServerContext& context, + const std::string& instanceId, + ResourceType level, + const DicomMap& query) + { + switch (level) + { + case ResourceType_Patient: + if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + return NULL; + } + + break; + + case ResourceType_Study: + if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && + !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) && + !query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) + { + return NULL; + } + + break; + + case ResourceType_Series: + if (!query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) + { + return NULL; + } + + break; + + default: + return NULL; + } + + std::string parent; + if (!context.GetIndex().LookupParent(parent, instanceId, level)) + { + throw OrthancException(ErrorCode_UnknownResource); // The resource was deleted in between + } + + std::auto_ptr<DicomMap> result(new DicomMap); + + switch (level) + { + case ResourceType_Patient: + ComputePatientCounters(*result, context.GetIndex(), parent, query); + break; + + case ResourceType_Study: + ComputeStudyCounters(*result, context, parent, query); + break; + + case ResourceType_Series: + ComputeSeriesCounters(*result, context.GetIndex(), parent, query); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return result.release(); + } + + static void AddAnswer(DicomFindAnswers& answers, const Json::Value& resource, - const DicomArray& query) + const DicomArray& query, + const std::list<DicomTag>& sequencesToReturn, + const DicomMap* counters) { DicomMap result; for (size_t i = 0; i < query.GetSize(); i++) { - // Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL) { + // Fix issue 30 on Google Code (QR response missing "Query/Retrieve Level" (008,0052)) result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue()); } else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET) { + // Do not include the encoding, this is handled by class ParsedDicomFile } else { @@ -68,56 +385,169 @@ if (resource.isMember(tag)) { value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); - result.SetValue(query.GetElement(i).GetTag(), value); + result.SetValue(query.GetElement(i).GetTag(), value, false); } else { - result.SetValue(query.GetElement(i).GetTag(), ""); + result.SetValue(query.GetElement(i).GetTag(), "", false); } } } - if (result.GetSize() == 0) + if (counters != NULL) + { + DicomArray tmp(*counters); + for (size_t i = 0; i < tmp.GetSize(); i++) + { + result.SetValue(tmp.GetElement(i).GetTag(), tmp.GetElement(i).GetValue().GetContent(), false); + } + } + + if (result.GetSize() == 0 && + sequencesToReturn.empty()) { LOG(WARNING) << "The C-FIND request does not return any DICOM tag"; } + else if (sequencesToReturn.empty()) + { + answers.Add(result); + } else { - answers.Add(result); + ParsedDicomFile dicom(result); + + for (std::list<DicomTag>::const_iterator tag = sequencesToReturn.begin(); + tag != sequencesToReturn.end(); ++tag) + { + const Json::Value& source = resource[tag->Format()]; + + if (source.type() == Json::objectValue && + source.isMember("Type") && + source.isMember("Value") && + source["Type"].asString() == "Sequence" && + source["Value"].type() == Json::arrayValue) + { + Json::Value content = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < source["Value"].size(); i++) + { + Json::Value item; + ServerToolbox::SimplifyTags(item, source["Value"][i], DicomToJsonFormat_Short); + content.append(item); + } + + dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent); + } + } + + answers.Add(dicom); } } - bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, + + bool OrthancFindRequestHandler::FilterQueryTag(std::string& value /* can be modified */, + ResourceType level, + const DicomTag& tag, + ModalityManufacturer manufacturer) + { + // Whatever the manufacturer, remove the GenericGroupLength tags + // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.2.html + // https://bitbucket.org/sjodogne/orthanc/issues/31/ + if (tag.GetElement() == 0x0000) + { + return false; + } + + switch (manufacturer) + { + case ModalityManufacturer_Vitrea: + // Following Denis Nesterov's mail on 2015-11-30 + if (tag == DicomTag(0x5653, 0x0010)) // "PrivateCreator = Vital Images SW 3.4" + { + return false; + } + + break; + + default: + break; + } + + return true; + } + + + bool OrthancFindRequestHandler::ApplyLuaFilter(DicomMap& target, + const DicomMap& source, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) + { + static const char* LUA_CALLBACK = "IncomingFindRequestFilter"; + + LuaScripting::Lock lock(context_.GetLuaScripting()); + + if (!lock.GetLua().IsExistingFunction(LUA_CALLBACK)) + { + return false; + } + else + { + Json::Value origin; + FormatOrigin(origin, remoteIp, remoteAet, calledAet, manufacturer); + + LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK); + call.PushDicom(source); + call.PushJson(origin); + FromDcmtkBridge::ExecuteToDicom(target, call); + + return true; + } + } + + + OrthancFindRequestHandler::OrthancFindRequestHandler(ServerContext& context) : + context_(context), + maxResults_(0), + maxInstances_(0) + { + } + + + void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, + const std::list<DicomTag>& sequencesToReturn, const std::string& remoteIp, - const std::string& remoteAet) + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) { /** - * Ensure that the calling modality is known to Orthanc. + * Possibly apply the user-supplied Lua filter. **/ - RemoteModalityParameters modality; + DicomMap lua; + const DicomMap* filteredInput = &input; - if (!Configuration::LookupDicomModalityUsingAETitle(modality, remoteAet)) + if (ApplyLuaFilter(lua, input, remoteIp, remoteAet, calledAet, manufacturer)) { - throw OrthancException(ErrorCode_UnknownModality); + filteredInput = &lua; } - // ModalityManufacturer manufacturer = modality.GetManufacturer(); - - bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false); - /** * Retrieve the query level. **/ - const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); + assert(filteredInput != NULL); + const DicomValue* levelTmp = filteredInput->TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); if (levelTmp == NULL || levelTmp->IsNull() || levelTmp->IsBinary()) { + LOG(ERROR) << "C-FIND request without the tag 0008,0052 (QueryRetrieveLevel)"; throw OrthancException(ErrorCode_BadRequest); } @@ -132,7 +562,7 @@ } - DicomArray query(input); + DicomArray query(*filteredInput); LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level); for (size_t i = 0; i < query.GetSize(); i++) @@ -140,47 +570,66 @@ if (!query.GetElement(i).GetValue().IsNull()) { LOG(INFO) << " " << query.GetElement(i).GetTag() - << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) + << " " << FromDcmtkBridge::GetTagName(query.GetElement(i)) << " = " << query.GetElement(i).GetValue().GetContent(); } } + for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin(); + it != sequencesToReturn.end(); ++it) + { + LOG(INFO) << " (" << it->Format() + << ") " << FromDcmtkBridge::GetTagName(*it, "") + << " : sequence tag whose content will be copied"; + } + /** * Build up the query object. **/ - LookupResource finder(level); + LookupResource lookup(level); + + const bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false); for (size_t i = 0; i < query.GetSize(); i++) { - const DicomTag tag = query.GetElement(i).GetTag(); + const DicomElement& element = query.GetElement(i); + const DicomTag tag = element.GetTag(); - if (query.GetElement(i).GetValue().IsNull() || + if (element.GetValue().IsNull() || tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL || tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) { continue; } - std::string value = query.GetElement(i).GetValue().GetContent(); + std::string value = element.GetValue().GetContent(); if (value.size() == 0) { // An empty string corresponds to a "*" wildcard constraint, so we ignore it continue; } - ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag); + if (FilterQueryTag(value, level, tag, manufacturer)) + { + ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); - // DICOM specifies that searches must be case sensitive, except - // for tags with a PN value representation - bool sensitive = true; - if (vr == ValueRepresentation_PatientName) + // DICOM specifies that searches must be case sensitive, except + // for tags with a PN value representation + bool sensitive = true; + if (vr == ValueRepresentation_PersonName) + { + sensitive = caseSensitivePN; + } + + lookup.AddDicomConstraint(tag, value, sensitive); + } + else { - sensitive = caseSensitivePN; + LOG(INFO) << "Because of a patch for the manufacturer of the remote modality, " + << "ignoring constraint on tag (" << tag.Format() << ") " << FromDcmtkBridge::GetTagName(element); } - - finder.AddDicomConstraint(tag, value, sensitive); } @@ -188,36 +637,57 @@ * Run the query. **/ - size_t maxResults = (level == ResourceType_Instance) ? maxInstances_ : maxResults_; + size_t limit = (level == ResourceType_Instance) ? maxInstances_ : maxResults_; + // TODO - Use ServerContext::Apply() at this point, in order to + // share the code with the "/tools/find" REST URI std::vector<std::string> resources, instances; - context_.GetIndex().FindCandidates(resources, instances, finder); + context_.GetIndex().FindCandidates(resources, instances, lookup); + + LOG(INFO) << "Number of candidate resources after fast DB filtering: " << resources.size(); assert(resources.size() == instances.size()); - bool finished = true; + bool complete = true; for (size_t i = 0; i < instances.size(); i++) { + // TODO - Don't read the full JSON from the disk if only "main + // DICOM tags" are to be returned Json::Value dicom; - context_.ReadJson(dicom, instances[i]); + context_.ReadDicomAsJson(dicom, instances[i]); - if (finder.IsMatch(dicom)) + if (lookup.IsMatch(dicom)) { - if (maxResults != 0 && - answers.GetSize() >= maxResults) + if (limit != 0 && + answers.GetSize() >= limit) { - finished = false; + complete = false; break; } else { - AddAnswer(answers, dicom, query); + std::auto_ptr<DicomMap> counters(ComputeCounters(context_, instances[i], level, *filteredInput)); + AddAnswer(answers, dicom, query, sequencesToReturn, counters.get()); } } } LOG(INFO) << "Number of matching resources: " << answers.GetSize(); - return finished; + answers.SetComplete(complete); + } + + + void OrthancFindRequestHandler::FormatOrigin(Json::Value& origin, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) + { + origin = Json::objectValue; + origin["RemoteIp"] = remoteIp; + origin["RemoteAet"] = remoteAet; + origin["CalledAet"] = calledAet; + origin["Manufacturer"] = EnumerationToString(manufacturer); } }
--- a/OrthancServer/OrthancFindRequestHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -31,7 +32,7 @@ #pragma once -#include "DicomProtocol/IFindRequestHandler.h" +#include "../Core/DicomNetworking/IFindRequestHandler.h" #include "ServerContext.h" @@ -41,24 +42,34 @@ { private: ServerContext& context_; - unsigned int maxResults_; - unsigned int maxInstances_; + unsigned int maxResults_; + unsigned int maxInstances_; bool HasReachedLimit(const DicomFindAnswers& answers, ResourceType level) const; + bool FilterQueryTag(std::string& value /* can be modified */, + ResourceType level, + const DicomTag& tag, + ModalityManufacturer manufacturer); + + bool ApplyLuaFilter(DicomMap& target, + const DicomMap& source, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer); + public: - OrthancFindRequestHandler(ServerContext& context) : - context_(context), - maxResults_(0), - maxInstances_(0) - { - } + OrthancFindRequestHandler(ServerContext& context); - virtual bool Handle(DicomFindAnswers& answers, + virtual void Handle(DicomFindAnswers& answers, const DicomMap& input, + const std::list<DicomTag>& sequencesToReturn, const std::string& remoteIp, - const std::string& remoteAet); + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer); unsigned int GetMaxResults() const { @@ -79,5 +90,11 @@ { maxInstances_ = instances; } + + static void FormatOrigin(Json::Value& origin, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer); }; }
--- a/OrthancServer/OrthancHttpHandler.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancHttpHandler.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancHttpHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancInitialization.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -31,7 +32,14 @@ #include "PrecompiledHeadersServer.h" + +#if defined(_WIN32) +// "Please include winsock2.h before windows.h" +# include <winsock2.h> +#endif + #include "OrthancInitialization.h" +#include "ServerContext.h" #include "../Core/HttpClient.h" #include "../Core/Logging.h" @@ -41,41 +49,25 @@ #include "ServerEnumerations.h" #include "DatabaseWrapper.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include <boost/lexical_cast.hpp> #include <boost/filesystem.hpp> #include <curl/curl.h> -#include <boost/thread.hpp> - +#include <boost/thread/recursive_mutex.hpp> -#if ORTHANC_SSL_ENABLED == 1 -// For OpenSSL initialization and finalization -#include <openssl/conf.h> -#include <openssl/engine.h> -#include <openssl/err.h> -#include <openssl/evp.h> -#include <openssl/ssl.h> -#endif +#include <dcmtk/dcmnet/dul.h> // For dcmDisableGethostbyaddr() -#if ORTHANC_JPEG_ENABLED == 1 -#include <dcmtk/dcmjpeg/djdecode.h> -#endif - - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 -#include <dcmtk/dcmjpls/djdecode.h> -#endif - namespace Orthanc { - static boost::mutex globalMutex_; + static boost::recursive_mutex globalMutex_; static Json::Value configuration_; static boost::filesystem::path defaultDirectory_; static std::string configurationAbsolutePath_; static FontRegistry fontRegistry_; + static const char* configurationFileArg_ = NULL; static std::string GetGlobalStringParameterInternal(const std::string& parameter, @@ -123,7 +115,8 @@ - static void AddFileToConfiguration(const boost::filesystem::path& path) + static void AddFileToConfiguration(Json::Value& target, + const boost::filesystem::path& path) { LOG(WARNING) << "Reading the configuration from: " << path; @@ -131,7 +124,7 @@ { std::string content; - Toolbox::ReadFile(content, path.string()); + SystemToolbox::ReadFile(content, path.string()); Json::Value tmp; Json::Reader reader; @@ -145,30 +138,31 @@ Toolbox::CopyJsonWithoutComments(config, tmp); } - if (configuration_.size() == 0) + if (target.size() == 0) { - configuration_ = config; + target = config; } else { Json::Value::Members members = config.getMemberNames(); for (Json::Value::ArrayIndex i = 0; i < members.size(); i++) { - if (configuration_.isMember(members[i])) + if (target.isMember(members[i])) { LOG(ERROR) << "The configuration section \"" << members[i] << "\" is defined in 2 different configuration files"; throw OrthancException(ErrorCode_BadFileFormat); } else { - configuration_[members[i]] = config[members[i]]; + target[members[i]] = config[members[i]]; } } } } - static void ScanFolderForConfiguration(const char* folder) + static void ScanFolderForConfiguration(Json::Value& target, + const char* folder) { using namespace boost::filesystem; @@ -186,19 +180,17 @@ if (extension == ".json") { - AddFileToConfiguration(it->path().string()); + AddFileToConfiguration(target, it->path().string()); } } } } - static void ReadGlobalConfiguration(const char* configurationFile) + static void ReadConfiguration(Json::Value& target, + const char* configurationFile) { - // Prepare the default configuration - defaultDirectory_ = boost::filesystem::current_path(); - configuration_ = Json::objectValue; - configurationAbsolutePath_ = ""; + target = Json::objectValue; if (configurationFile) { @@ -210,15 +202,11 @@ if (boost::filesystem::is_directory(configurationFile)) { - defaultDirectory_ = boost::filesystem::path(configurationFile); - configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string(); - ScanFolderForConfiguration(configurationFile); + ScanFolderForConfiguration(target, configurationFile); } else { - defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path(); - configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string(); - AddFileToConfiguration(configurationFile); + AddFileToConfiguration(target, configurationFile); } } else @@ -235,14 +223,73 @@ boost::filesystem::path p = ORTHANC_PATH; p /= "Resources"; p /= "Configuration.json"; - configurationAbsolutePath_ = boost::filesystem::absolute(p).string(); + + AddFileToConfiguration(target, p); +#endif + } + } + + + + static void ReadGlobalConfiguration(const char* configurationFile) + { + // Read the content of the configuration + configurationFileArg_ = configurationFile; + ReadConfiguration(configuration_, configurationFile); + + // Adapt the paths to the configurations + defaultDirectory_ = boost::filesystem::current_path(); + configurationAbsolutePath_ = ""; - AddFileToConfiguration(p); + if (configurationFile) + { + if (boost::filesystem::is_directory(configurationFile)) + { + defaultDirectory_ = boost::filesystem::path(configurationFile); + configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string(); + } + else + { + defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path(); + configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string(); + } + } + else + { +#if ORTHANC_STANDALONE != 1 + // In a non-standalone build, we use the + // "Resources/Configuration.json" from the Orthanc source code + + boost::filesystem::path p = ORTHANC_PATH; + p /= "Resources"; + p /= "Configuration.json"; + configurationAbsolutePath_ = boost::filesystem::absolute(p).string(); #endif } } + static void ValidateGlobalConfiguration() + { + std::set<std::string> ids; + + Configuration::GetListOfOrthancPeers(ids); + for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + WebServiceParameters peer; + Configuration::GetOrthancPeer(peer, *it); + peer.CheckClientCertificate(); + } + + Configuration::GetListOfDicomModalities(ids); + for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + RemoteModalityParameters modality; + Configuration::GetDicomModalityUsingSymbolicName(modality, *it); + } + } + + static void RegisterUserMetadata() { if (configuration_.isMember("UserMetadata")) @@ -252,24 +299,26 @@ Json::Value::Members members = parameter.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { - std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString(); - LOG(INFO) << "Registering user-defined metadata: " << info; + const std::string& name = members[i]; - if (!parameter[members[i]].asBool()) + if (!parameter[name].isInt()) { - LOG(ERROR) << "Not a number in this user-defined metadata: " << info; + LOG(ERROR) << "Not a number in this user-defined metadata: " << name; throw OrthancException(ErrorCode_BadParameterType); } - int metadata = parameter[members[i]].asInt(); + int metadata = parameter[name].asInt(); + + LOG(INFO) << "Registering user-defined metadata: " << name << " (index " + << metadata << ")"; try { - RegisterUserMetadata(metadata, members[i]); + RegisterUserMetadata(metadata, name); } catch (OrthancException&) { - LOG(ERROR) << "Cannot register this user-defined metadata: " << info; + LOG(ERROR) << "Cannot register this user-defined metadata: " << name; throw; } } @@ -286,24 +335,39 @@ Json::Value::Members members = parameter.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { - std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString(); - LOG(INFO) << "Registering user-defined attachment type: " << info; + const std::string& name = members[i]; + std::string mime = "application/octet-stream"; + + const Json::Value& value = parameter[name]; + int contentType; - if (!parameter[members[i]].asBool()) + if (value.isArray() && + value.size() == 2 && + value[0].isInt() && + value[1].isString()) { - LOG(ERROR) << "Not a number in this user-defined attachment type: " << info; + contentType = value[0].asInt(); + mime = value[1].asString(); + } + else if (value.isInt()) + { + contentType = value.asInt(); + } + else + { + LOG(ERROR) << "Not a number in this user-defined attachment type: " << name; throw OrthancException(ErrorCode_BadParameterType); } - int contentType = parameter[members[i]].asInt(); + LOG(INFO) << "Registering user-defined attachment type: " << name << " (index " + << contentType << ") with MIME type \"" << mime << "\""; try { - RegisterUserContentType(contentType, members[i]); + RegisterUserContentType(contentType, name, mime); } catch (OrthancException&) { - LOG(ERROR) << "Cannot register this user-defined attachment type: " << info; throw; } } @@ -311,86 +375,156 @@ } + static void LoadCustomDictionary(const Json::Value& configuration) + { + if (configuration.type() != Json::objectValue || + !configuration.isMember("Dictionary") || + configuration["Dictionary"].type() != Json::objectValue) + { + return; + } + + Json::Value::Members tags(configuration["Dictionary"].getMemberNames()); + + for (Json::Value::ArrayIndex i = 0; i < tags.size(); i++) + { + const Json::Value& content = configuration["Dictionary"][tags[i]]; + if (content.type() != Json::arrayValue || + content.size() < 2 || + content.size() > 5 || + content[0].type() != Json::stringValue || + content[1].type() != Json::stringValue || + (content.size() >= 3 && content[2].type() != Json::intValue) || + (content.size() >= 4 && content[3].type() != Json::intValue) || + (content.size() >= 5 && content[4].type() != Json::stringValue)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + DicomTag tag(FromDcmtkBridge::ParseTag(tags[i])); + ValueRepresentation vr = StringToValueRepresentation(content[0].asString(), true); + std::string name = content[1].asString(); + unsigned int minMultiplicity = (content.size() >= 2) ? content[2].asUInt() : 1; + unsigned int maxMultiplicity = (content.size() >= 3) ? content[3].asUInt() : 1; + std::string privateCreator = (content.size() >= 4) ? content[4].asString() : ""; + + FromDcmtkBridge::RegisterDictionaryTag(tag, vr, name, minMultiplicity, maxMultiplicity, privateCreator); + } + } + + + static void ConfigurePkcs11(const Json::Value& config) + { + if (config.type() != Json::objectValue || + !config.isMember("Module") || + config["Module"].type() != Json::stringValue) + { + LOG(ERROR) << "No path to the PKCS#11 module (DLL or .so) is provided for HTTPS client authentication"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::string pin; + if (config.isMember("Pin")) + { + if (config["Pin"].type() == Json::stringValue) + { + pin = config["Pin"].asString(); + } + else + { + LOG(ERROR) << "The PIN number in the PKCS#11 configuration must be a string"; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + bool verbose = false; + if (config.isMember("Verbose")) + { + if (config["Verbose"].type() == Json::booleanValue) + { + verbose = config["Verbose"].asBool(); + } + else + { + LOG(ERROR) << "The Verbose option in the PKCS#11 configuration must be a Boolean"; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + HttpClient::InitializePkcs11(config["Module"].asString(), pin, verbose); + } + + void OrthancInitialize(const char* configurationFile) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); -#if ORTHANC_SSL_ENABLED == 1 - // https://wiki.openssl.org/index.php/Library_Initialization - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - ERR_load_crypto_strings(); - - curl_global_init(CURL_GLOBAL_ALL); -#else - curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL); -#endif + Toolbox::InitializeOpenSsl(); InitializeServerEnumerations(); // Read the user-provided configuration ReadGlobalConfiguration(configurationFile); + ValidateGlobalConfiguration(); - HttpClient::GlobalInitialize(GetGlobalBoolParameterInternal("HttpsVerifyPeers", true), - GetGlobalStringParameterInternal("HttpsCACertificates", "")); + if (configuration_.isMember("Locale")) + { + std::string locale = GetGlobalStringParameterInternal("Locale", ""); + Toolbox::InitializeGlobalLocale(configuration_["Locale"].asCString()); + } + else + { + Toolbox::InitializeGlobalLocale(NULL); + } + + if (configuration_.isMember("DefaultEncoding")) + { + std::string encoding = GetGlobalStringParameterInternal("DefaultEncoding", ""); + SetDefaultDicomEncoding(StringToEncoding(encoding.c_str())); + } + else + { + SetDefaultDicomEncoding(ORTHANC_DEFAULT_DICOM_ENCODING); + } + + if (configuration_.isMember("Pkcs11")) + { + ConfigurePkcs11(configuration_["Pkcs11"]); + } + + HttpClient::GlobalInitialize(); RegisterUserMetadata(); RegisterUserContentType(); - FromDcmtkBridge::InitializeDictionary(); + FromDcmtkBridge::InitializeDictionary(GetGlobalBoolParameterInternal("LoadPrivateDictionary", true)); + LoadCustomDictionary(configuration_); -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 - LOG(WARNING) << "Registering JPEG Lossless codecs"; - DJLSDecoderRegistration::registerCodecs(); -#endif - -#if ORTHANC_JPEG_ENABLED == 1 - LOG(WARNING) << "Registering JPEG codecs"; - DJDecoderRegistration::registerCodecs(); -#endif + FromDcmtkBridge::InitializeCodecs(); fontRegistry_.AddFromResource(EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); + + /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ + dcmDisableGethostbyaddr.set(OFTrue); } void OrthancFinalize() { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); HttpClient::GlobalFinalize(); - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 - // Unregister JPEG-LS codecs - DJLSDecoderRegistration::cleanup(); -#endif - -#if ORTHANC_JPEG_ENABLED == 1 - // Unregister JPEG codecs - DJDecoderRegistration::cleanup(); -#endif - - curl_global_cleanup(); - -#if ORTHANC_SSL_ENABLED == 1 - // Finalize OpenSSL - // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup - FIPS_mode_set(0); - ENGINE_cleanup(); - CONF_modules_unload(1); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - ERR_remove_state(0); - ERR_free_strings(); -#endif + FromDcmtkBridge::FinalizeCodecs(); + Toolbox::FinalizeOpenSsl(); + Toolbox::FinalizeGlobalLocale(); } std::string Configuration::GetGlobalStringParameter(const std::string& parameter, const std::string& defaultValue) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); return GetGlobalStringParameterInternal(parameter, defaultValue); } @@ -398,7 +532,7 @@ int Configuration::GetGlobalIntegerParameter(const std::string& parameter, int defaultValue) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); if (configuration_.isMember(parameter)) { @@ -419,10 +553,27 @@ } + unsigned int Configuration::GetGlobalUnsignedIntegerParameter(const std::string& parameter, + unsigned int defaultValue) + { + int v = GetGlobalIntegerParameter(parameter, defaultValue); + + if (v < 0) + { + LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a positive integer"; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return static_cast<unsigned int>(v); + } + } + + bool Configuration::GetGlobalBoolParameter(const std::string& parameter, bool defaultValue) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); return GetGlobalBoolParameterInternal(parameter, defaultValue); } @@ -430,7 +581,7 @@ void Configuration::GetDicomModalityUsingSymbolicName(RemoteModalityParameters& modality, const std::string& name) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); if (!configuration_.isMember("DicomModalities")) { @@ -448,7 +599,7 @@ try { - modality.FromJson(modalities[name]); + modality.Unserialize(modalities[name]); } catch (OrthancException&) { @@ -459,16 +610,14 @@ } - - void Configuration::GetOrthancPeer(OrthancPeerParameters& peer, + bool Configuration::GetOrthancPeer(WebServiceParameters& peer, const std::string& name) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); if (!configuration_.isMember("OrthancPeers")) { - LOG(ERROR) << "No peer with symbolic name: " << name; - throw OrthancException(ErrorCode_InexistentItem); + return false; } try @@ -477,11 +626,13 @@ if (modalities.type() != Json::objectValue || !modalities.isMember(name)) { - LOG(ERROR) << "No peer with symbolic name: " << name; - throw OrthancException(ErrorCode_InexistentItem); + return false; } - - peer.FromJson(modalities[name]); + else + { + peer.Unserialize(modalities[name]); + return true; + } } catch (OrthancException&) { @@ -496,7 +647,7 @@ const char* parameter, bool onlyAlphanumeric) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); target.clear(); @@ -535,6 +686,8 @@ void Configuration::GetListOfDicomModalities(std::set<std::string>& target) { + target.clear(); + if (!ReadKeys(target, "DicomModalities", true)) { LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of the modalities"; @@ -545,6 +698,8 @@ void Configuration::GetListOfOrthancPeers(std::set<std::string>& target) { + target.clear(); + if (!ReadKeys(target, "OrthancPeers", true)) { LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of Orthanc peers"; @@ -556,7 +711,7 @@ void Configuration::SetupRegisteredUsers(MongooseServer& httpServer) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); httpServer.ClearUsers(); @@ -610,7 +765,7 @@ std::string Configuration::InterpretStringParameterAsPath(const std::string& parameter) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); return InterpretRelativePath(defaultDirectory_.string(), parameter); } @@ -618,7 +773,7 @@ void Configuration::GetGlobalListOfStringsParameter(std::list<std::string>& target, const std::string& key) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); target.clear(); @@ -688,10 +843,30 @@ } - bool Configuration::IsKnownAETitle(const std::string& aet) + bool Configuration::IsKnownAETitle(const std::string& aet, + const std::string& ip) { RemoteModalityParameters modality; - return LookupDicomModalityUsingAETitle(modality, aet); + + if (!LookupDicomModalityUsingAETitle(modality, aet)) + { + LOG(WARNING) << "Modality \"" << aet + << "\" is not listed in the \"DicomModalities\" configuration option"; + return false; + } + else if (!Configuration::GetGlobalBoolParameter("DicomCheckModalityHost", false) || + ip == modality.GetHost()) + { + return true; + } + else + { + LOG(WARNING) << "Forbidding access from AET \"" << aet + << "\" given its hostname (" << ip << ") does not match " + << "the \"DicomModalities\" configuration option (" + << modality.GetHost() << " was expected)"; + return false; + } } @@ -720,96 +895,140 @@ } - void Configuration::UpdateModality(const std::string& symbolicName, + void Configuration::UpdateModality(ServerContext& context, + const std::string& symbolicName, const RemoteModalityParameters& modality) { - boost::mutex::scoped_lock lock(globalMutex_); + { + boost::recursive_mutex::scoped_lock lock(globalMutex_); + + if (!configuration_.isMember("DicomModalities")) + { + configuration_["DicomModalities"] = Json::objectValue; + } - if (!configuration_.isMember("DicomModalities")) - { - configuration_["DicomModalities"] = Json::objectValue; + Json::Value& modalities = configuration_["DicomModalities"]; + if (modalities.type() != Json::objectValue) + { + LOG(ERROR) << "Bad file format for modality: " << symbolicName; + throw OrthancException(ErrorCode_BadFileFormat); + } + + modalities.removeMember(symbolicName); + + Json::Value v; + modality.Serialize(v, true /* force advanced format */); + modalities[symbolicName] = v; } - Json::Value& modalities = configuration_["DicomModalities"]; - if (modalities.type() != Json::objectValue) +#if ORTHANC_ENABLE_PLUGINS == 1 + if (context.HasPlugins()) { - LOG(ERROR) << "Bad file format for modality: " << symbolicName; - throw OrthancException(ErrorCode_BadFileFormat); + context.GetPlugins().SignalUpdatedModalities(); } - - modalities.removeMember(symbolicName); - - Json::Value v; - modality.ToJson(v); - modalities[symbolicName] = v; +#endif } - void Configuration::RemoveModality(const std::string& symbolicName) + void Configuration::RemoveModality(ServerContext& context, + const std::string& symbolicName) { - boost::mutex::scoped_lock lock(globalMutex_); + { + boost::recursive_mutex::scoped_lock lock(globalMutex_); - if (!configuration_.isMember("DicomModalities")) - { - LOG(ERROR) << "No modality with symbolic name: " << symbolicName; - throw OrthancException(ErrorCode_BadFileFormat); + if (!configuration_.isMember("DicomModalities")) + { + LOG(ERROR) << "No modality with symbolic name: " << symbolicName; + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& modalities = configuration_["DicomModalities"]; + if (modalities.type() != Json::objectValue) + { + LOG(ERROR) << "Bad file format for the \"DicomModalities\" configuration section"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + modalities.removeMember(symbolicName.c_str()); } - Json::Value& modalities = configuration_["DicomModalities"]; - if (modalities.type() != Json::objectValue) +#if ORTHANC_ENABLE_PLUGINS == 1 + if (context.HasPlugins()) { - LOG(ERROR) << "Bad file format for the \"DicomModalities\" configuration section"; - throw OrthancException(ErrorCode_BadFileFormat); + context.GetPlugins().SignalUpdatedModalities(); } - - modalities.removeMember(symbolicName.c_str()); +#endif } - void Configuration::UpdatePeer(const std::string& symbolicName, - const OrthancPeerParameters& peer) + void Configuration::UpdatePeer(ServerContext& context, + const std::string& symbolicName, + const WebServiceParameters& peer) { - boost::mutex::scoped_lock lock(globalMutex_); + peer.CheckClientCertificate(); + + { + boost::recursive_mutex::scoped_lock lock(globalMutex_); + + if (!configuration_.isMember("OrthancPeers")) + { + LOG(ERROR) << "No peer with symbolic name: " << symbolicName; + configuration_["OrthancPeers"] = Json::objectValue; + } - if (!configuration_.isMember("OrthancPeers")) - { - LOG(ERROR) << "No peer with symbolic name: " << symbolicName; - configuration_["OrthancPeers"] = Json::objectValue; + Json::Value& peers = configuration_["OrthancPeers"]; + if (peers.type() != Json::objectValue) + { + LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + peers.removeMember(symbolicName); + + Json::Value v; + peer.Serialize(v, + false /* use simple format if possible */, + true /* include passwords */); + peers[symbolicName] = v; } - Json::Value& peers = configuration_["OrthancPeers"]; - if (peers.type() != Json::objectValue) +#if ORTHANC_ENABLE_PLUGINS == 1 + if (context.HasPlugins()) { - LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; - throw OrthancException(ErrorCode_BadFileFormat); + context.GetPlugins().SignalUpdatedPeers(); } - - peers.removeMember(symbolicName); - - Json::Value v; - peer.ToJson(v); - peers[symbolicName] = v; +#endif } - void Configuration::RemovePeer(const std::string& symbolicName) + void Configuration::RemovePeer(ServerContext& context, + const std::string& symbolicName) { - boost::mutex::scoped_lock lock(globalMutex_); + { + boost::recursive_mutex::scoped_lock lock(globalMutex_); - if (!configuration_.isMember("OrthancPeers")) - { - LOG(ERROR) << "No peer with symbolic name: " << symbolicName; - throw OrthancException(ErrorCode_BadFileFormat); + if (!configuration_.isMember("OrthancPeers")) + { + LOG(ERROR) << "No peer with symbolic name: " << symbolicName; + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& peers = configuration_["OrthancPeers"]; + if (peers.type() != Json::objectValue) + { + LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + peers.removeMember(symbolicName.c_str()); } - Json::Value& peers = configuration_["OrthancPeers"]; - if (peers.type() != Json::objectValue) +#if ORTHANC_ENABLE_PLUGINS == 1 + if (context.HasPlugins()) { - LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; - throw OrthancException(ErrorCode_BadFileFormat); + context.GetPlugins().SignalUpdatedPeers(); } - - peers.removeMember(symbolicName.c_str()); +#endif } @@ -834,7 +1053,7 @@ { boost::filesystem::create_directories(indexDirectory); } - catch (boost::filesystem::filesystem_error) + catch (boost::filesystem::filesystem_error&) { } @@ -926,7 +1145,7 @@ void Configuration::GetConfiguration(Json::Value& result) { - boost::mutex::scoped_lock lock(globalMutex_); + boost::recursive_mutex::scoped_lock lock(globalMutex_); result = configuration_; } @@ -945,4 +1164,33 @@ { return fontRegistry_; } + + + void Configuration::SetDefaultEncoding(Encoding encoding) + { + SetDefaultDicomEncoding(encoding); + + { + // Propagate the encoding to the configuration file that is + // stored in memory + boost::recursive_mutex::scoped_lock lock(globalMutex_); + configuration_["DefaultEncoding"] = EnumerationToString(encoding); + } + } + + + bool Configuration::HasConfigurationChanged() + { + Json::Value starting; + GetConfiguration(starting); + + Json::Value current; + ReadConfiguration(current, configurationFileArg_); + + Json::FastWriter writer; + std::string a = writer.write(starting); + std::string b = writer.write(current); + + return a != b; + } }
--- a/OrthancServer/OrthancInitialization.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancInitialization.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,22 +37,30 @@ #include <set> #include <json/json.h> #include <stdint.h> + +#include "../Core/FileStorage/IStorageArea.h" #include "../Core/HttpServer/MongooseServer.h" -#include "DicomProtocol/RemoteModalityParameters.h" +#include "../Core/Images/FontRegistry.h" +#include "../Core/WebServiceParameters.h" +#include "../Core/DicomNetworking/RemoteModalityParameters.h" + +#include "IDatabaseWrapper.h" #include "ServerEnumerations.h" -#include "OrthancPeerParameters.h" -#include "IDatabaseWrapper.h" -#include "../Core/FileStorage/IStorageArea.h" -#include "../Core/Images/FontRegistry.h" + namespace Orthanc { + class ServerContext; + void OrthancInitialize(const char* configurationFile = NULL); void OrthancFinalize(); class Configuration { + private: + Configuration(); // Forbidden, this is a static class + public: static std::string GetGlobalStringParameter(const std::string& parameter, const std::string& defaultValue); @@ -59,6 +68,9 @@ static int GetGlobalIntegerParameter(const std::string& parameter, int defaultValue); + static unsigned int GetGlobalUnsignedIntegerParameter(const std::string& parameter, + unsigned int defaultValue); + static bool GetGlobalBoolParameter(const std::string& parameter, bool defaultValue); @@ -68,7 +80,7 @@ static bool LookupDicomModalityUsingAETitle(RemoteModalityParameters& modality, const std::string& aet); - static void GetOrthancPeer(OrthancPeerParameters& peer, + static bool GetOrthancPeer(WebServiceParameters& peer, const std::string& name); static void GetListOfDicomModalities(std::set<std::string>& target); @@ -85,7 +97,8 @@ static void GetGlobalListOfStringsParameter(std::list<std::string>& target, const std::string& key); - static bool IsKnownAETitle(const std::string& aet); + static bool IsKnownAETitle(const std::string& aet, + const std::string& ip); static bool IsSameAETitle(const std::string& aet1, const std::string& aet2); @@ -94,15 +107,19 @@ static RemoteModalityParameters GetModalityUsingAet(const std::string& aet); - static void UpdateModality(const std::string& symbolicName, + static void UpdateModality(ServerContext& context, + const std::string& symbolicName, const RemoteModalityParameters& modality); - static void RemoveModality(const std::string& symbolicName); + static void RemoveModality(ServerContext& context, + const std::string& symbolicName); - static void UpdatePeer(const std::string& symbolicName, - const OrthancPeerParameters& peer); + static void UpdatePeer(ServerContext& context, + const std::string& symbolicName, + const WebServiceParameters& peer); - static void RemovePeer(const std::string& symbolicName); + static void RemovePeer(ServerContext& context, + const std::string& symbolicName); static const std::string& GetConfigurationAbsolutePath(); @@ -115,5 +132,9 @@ static void FormatConfiguration(std::string& result); static const FontRegistry& GetFontRegistry(); + + static void SetDefaultEncoding(Encoding encoding); + + static bool HasConfigurationChanged(); }; }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,10 @@ #include "OrthancMoveRequestHandler.h" #include "OrthancInitialization.h" -#include "FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/DicomFormat/DicomArray.h" #include "../Core/Logging.h" +#include "ServerJobs/DicomModalityStoreJob.h" namespace Orthanc { @@ -44,7 +46,7 @@ { // Anonymous namespace to avoid clashes between compilation modules - class OrthancMoveRequestIterator : public IMoveRequestIterator + class SynchronousMove : public IMoveRequestIterator { private: ServerContext& context_; @@ -52,16 +54,23 @@ std::vector<std::string> instances_; size_t position_; RemoteModalityParameters remote_; + std::string originatorAet_; + uint16_t originatorId_; + std::auto_ptr<DicomUserConnection> connection_; public: - OrthancMoveRequestIterator(ServerContext& context, - const std::string& aet, - const std::string& publicId) : + SynchronousMove(ServerContext& context, + const std::string& targetAet, + const std::string& publicId, + const std::string& originatorAet, + uint16_t originatorId) : context_(context), localAet_(context.GetDefaultLocalApplicationEntityTitle()), - position_(0) + position_(0), + originatorAet_(originatorAet), + originatorId_(originatorId) { - LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\""; + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << targetAet << "\""; std::list<std::string> tmp; context_.GetIndex().GetChildInstances(tmp, publicId); @@ -72,7 +81,7 @@ instances_.push_back(*it); } - remote_ = Configuration::GetModalityUsingAet(aet); + remote_ = Configuration::GetModalityUsingAet(targetAet); } virtual unsigned int GetSubOperationCount() const @@ -90,17 +99,78 @@ const std::string& id = instances_[position_++]; std::string dicom; - context_.ReadFile(dicom, id, FileContentType_Dicom); + context_.ReadDicom(dicom, id); + if (connection_.get() == NULL) { - ReusableDicomUserConnection::Locker locker - (context_.GetReusableDicomUserConnection(), localAet_, remote_); - locker.GetConnection().Store(dicom); + connection_.reset(new DicomUserConnection(localAet_, remote_)); } + connection_->Store(dicom, originatorAet_, originatorId_); + return Status_Success; } }; + + + class AsynchronousMove : public IMoveRequestIterator + { + private: + ServerContext& context_; + std::auto_ptr<DicomModalityStoreJob> job_; + size_t position_; + + public: + AsynchronousMove(ServerContext& context, + const std::string& targetAet, + const std::string& publicId, + const std::string& originatorAet, + uint16_t originatorId) : + context_(context), + job_(new DicomModalityStoreJob(context)), + position_(0) + { + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << targetAet << "\""; + + job_->SetDescription("C-MOVE"); + job_->SetPermissive(true); + job_->SetLocalAet(context.GetDefaultLocalApplicationEntityTitle()); + job_->SetRemoteModality(Configuration::GetModalityUsingAet(targetAet)); + + if (originatorId != 0) + { + job_->SetMoveOriginator(originatorAet, originatorId); + } + + std::list<std::string> tmp; + context_.GetIndex().GetChildInstances(tmp, publicId); + + job_->Reserve(tmp.size()); + + for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it) + { + job_->AddInstance(*it); + } + } + + virtual unsigned int GetSubOperationCount() const + { + return 1; + } + + virtual Status DoNext() + { + if (position_ == 0) + { + context_.GetJobsEngine().GetRegistry().Submit(job_.release(), 0 /* priority */); + return Status_Success; + } + else + { + return Status_Failure; + } + } + }; } @@ -162,10 +232,31 @@ } + static IMoveRequestIterator* CreateIterator(ServerContext& context, + const std::string& targetAet, + const std::string& publicId, + const std::string& originatorAet, + uint16_t originatorId) + { + bool synchronous = Configuration::GetGlobalBoolParameter("SynchronousCMove", false); + + if (synchronous) + { + return new SynchronousMove(context, targetAet, publicId, originatorAet, originatorId); + } + else + { + return new AsynchronousMove(context, targetAet, publicId, originatorAet, originatorId); + } + } + + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& targetAet, const DicomMap& input, - const std::string& remoteIp, - const std::string& remoteAet) + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet, + uint16_t originatorId) { LOG(WARNING) << "Move-SCU request received for AET \"" << targetAet << "\""; @@ -176,14 +267,12 @@ if (!query.GetElement(i).GetValue().IsNull()) { LOG(INFO) << " " << query.GetElement(i).GetTag() - << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) + << " " << FromDcmtkBridge::GetTagName(query.GetElement(i)) << " = " << query.GetElement(i).GetValue().GetContent(); } } } - - /** * Retrieve the query level. **/ @@ -207,7 +296,7 @@ LookupIdentifier(publicId, ResourceType_Study, input) || LookupIdentifier(publicId, ResourceType_Patient, input)) { - return new OrthancMoveRequestIterator(context_, targetAet, publicId); + return CreateIterator(context_, targetAet, publicId, originatorAet, originatorId); } else { @@ -228,7 +317,7 @@ if (LookupIdentifier(publicId, level, input)) { - return new OrthancMoveRequestIterator(context_, targetAet, publicId); + return CreateIterator(context_, targetAet, publicId, originatorAet, originatorId); } else {
--- a/OrthancServer/OrthancMoveRequestHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -31,7 +32,7 @@ #pragma once -#include "DicomProtocol/IMoveRequestHandler.h" +#include "../Core/DicomNetworking/IMoveRequestHandler.h" #include "ServerContext.h" namespace Orthanc @@ -53,7 +54,9 @@ virtual IMoveRequestIterator* Handle(const std::string& targetAet, const DicomMap& input, - const std::string& remoteIp, - const std::string& remoteAet); + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet, + uint16_t originatorId); }; }
--- a/OrthancServer/OrthancPeerParameters.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersServer.h" -#include "OrthancPeerParameters.h" - -#include "../Core/OrthancException.h" - -namespace Orthanc -{ - OrthancPeerParameters::OrthancPeerParameters() : - url_("http://localhost:8042/") - { - } - - - void OrthancPeerParameters::FromJson(const Json::Value& peer) - { - if (!peer.isArray() || - (peer.size() != 1 && peer.size() != 3)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - std::string url; - - try - { - url = peer.get(0u, "").asString(); - - if (peer.size() == 1) - { - SetUsername(""); - SetPassword(""); - } - else if (peer.size() == 3) - { - SetUsername(peer.get(1u, "").asString()); - SetPassword(peer.get(2u, "").asString()); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - catch (...) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (url.size() != 0 && url[url.size() - 1] != '/') - { - url += '/'; - } - - SetUrl(url); - } - - - void OrthancPeerParameters::ToJson(Json::Value& value) const - { - value = Json::arrayValue; - value.append(GetUrl()); - value.append(GetUsername()); - value.append(GetPassword()); - } -}
--- a/OrthancServer/OrthancPeerParameters.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <string> -#include <json/json.h> - -namespace Orthanc -{ - class OrthancPeerParameters - { - private: - std::string url_; - std::string username_; - std::string password_; - - public: - OrthancPeerParameters(); - - const std::string& GetUrl() const - { - return url_; - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - const std::string& GetUsername() const - { - return username_; - } - - void SetUsername(const std::string& username) - { - username_ = username; - } - - const std::string& GetPassword() const - { - return password_; - } - - void SetPassword(const std::string& password) - { - password_ = password; - } - - void FromJson(const Json::Value& peer); - - void ToJson(Json::Value& value) const; - }; -}
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,11 +34,13 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/Logging.h" -#include "../../Core/Uuid.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/SerializationToolbox.h" #include "../ServerContext.h" -#include "../OrthancInitialization.h" +#include "../ServerJobs/MergeStudyJob.h" +#include "../ServerJobs/ResourceModificationJob.h" +#include "../ServerJobs/SplitStudyJob.h" #include <boost/lexical_cast.hpp> #include <boost/algorithm/string/predicate.hpp> @@ -46,69 +49,7 @@ { // Modification of DICOM instances ------------------------------------------ - enum TagOperation - { - TagOperation_Keep, - TagOperation_Remove - }; - - static void ParseListOfTags(DicomModification& target, - const Json::Value& query, - TagOperation operation) - { - if (!query.isArray()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) - { - std::string name = query[i].asString(); - - DicomTag tag = FromDcmtkBridge::ParseTag(name); - - switch (operation) - { - case TagOperation_Keep: - target.Keep(tag); - VLOG(1) << "Keep: " << name << " " << tag << std::endl; - break; - - case TagOperation_Remove: - target.Remove(tag); - VLOG(1) << "Remove: " << name << " " << tag << std::endl; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - } - - - static void ParseReplacements(DicomModification& target, - const Json::Value& replacements) - { - if (!replacements.isObject()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - Json::Value::Members members = replacements.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - const std::string& name = members[i]; - const Json::Value& value = replacements[name]; - - DicomTag tag = FromDcmtkBridge::ParseTag(name); - target.Replace(tag, value); - - VLOG(1) << "Replace: " << name << " " << tag - << " == " << value.toStyledString() << std::endl; - } - } - - + static std::string GeneratePatientName(ServerContext& context) { uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); @@ -116,97 +57,45 @@ } - - bool OrthancRestApi::ParseModifyRequest(DicomModification& target, - const Json::Value& request) + static void ParseModifyRequest(Json::Value& request, + DicomModification& target, + const RestApiPostCall& call) { - if (request.isObject()) - { - if (request.isMember("RemovePrivateTags")) - { - target.SetRemovePrivateTags(true); - } + // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"},"Priority":9}' - if (request.isMember("Remove")) - { - ParseListOfTags(target, request["Remove"], TagOperation_Remove); - } - - if (request.isMember("Replace")) - { - ParseReplacements(target, request["Replace"]); - } - - return true; + if (call.ParseJsonRequest(request)) + { + target.ParseModifyRequest(request); } else { - return false; + throw OrthancException(ErrorCode_BadFileFormat); } } - static bool ParseModifyRequest(DicomModification& target, - const RestApiPostCall& call) - { - // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}' - - Json::Value request; - if (call.ParseJsonRequest(request)) - { - return OrthancRestApi::ParseModifyRequest(target, request); - } - else - { - return false; - } - } - - - static bool ParseAnonymizationRequest(DicomModification& target, + static void ParseAnonymizationRequest(Json::Value& request, + DicomModification& target, RestApiPostCall& call) { - // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm - - target.SetupAnonymization(); - std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME); - - Json::Value request; - if (call.ParseJsonRequest(request) && request.isObject()) - { - if (request.isMember("KeepPrivateTags")) - { - target.SetRemovePrivateTags(false); - } + // 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 - if (request.isMember("Remove")) - { - ParseListOfTags(target, request["Remove"], TagOperation_Remove); - } + if (call.ParseJsonRequest(request) && + request.isObject()) + { + bool patientNameReplaced; + target.ParseAnonymizationRequest(patientNameReplaced, request); - if (request.isMember("Replace")) - { - ParseReplacements(target, request["Replace"]); - } - - if (request.isMember("Keep")) - { - ParseListOfTags(target, request["Keep"], TagOperation_Keep); - } - - if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) && - target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) + if (patientNameReplaced) { // Overwrite the random Patient's Name by one that is more // user-friendly (provided none was specified by the user) target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)), true); } - - return true; } else { - return false; + throw OrthancException(ErrorCode_BadFileFormat); } } @@ -216,180 +105,44 @@ { std::string id = call.GetUriComponent("id", ""); - ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); + std::auto_ptr<ParsedDicomFile> modified; - std::auto_ptr<ParsedDicomFile> modified(locker.GetDicom().Clone()); + { + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); + modified.reset(locker.GetDicom().Clone(true)); + } + modification.Apply(*modified); modified->Answer(call.GetOutput()); } - static void AnonymizeOrModifyResource(DicomModification& modification, - MetadataType metadataType, - ChangeType changeType, - ResourceType resourceType, - RestApiPostCall& call) - { - bool isFirst = true; - Json::Value result(Json::objectValue); - - ServerContext& context = OrthancRestApi::GetContext(call); - - typedef std::list<std::string> Instances; - Instances instances; - std::string id = call.GetUriComponent("id", ""); - context.GetIndex().GetChildInstances(instances, id); - - if (instances.empty()) - { - return; - } - - - /** - * Loop over all the instances of the resource. - **/ - - for (Instances::const_iterator it = instances.begin(); - it != instances.end(); ++it) - { - LOG(INFO) << "Modifying instance " << *it; - - std::auto_ptr<ServerContext::DicomCacheLocker> locker; - - try - { - locker.reset(new ServerContext::DicomCacheLocker(OrthancRestApi::GetContext(call), *it)); - } - catch (OrthancException&) - { - // This child instance has been removed in between - continue; - } - - - ParsedDicomFile& original = locker->GetDicom(); - DicomInstanceHasher originalHasher = original.GetHasher(); - - - /** - * Compute the resulting DICOM instance. - **/ - - std::auto_ptr<ParsedDicomFile> modified(original.Clone()); - modification.Apply(*modified); - - DicomInstanceToStore toStore; - toStore.SetRestOrigin(call); - toStore.SetParsedDicomFile(*modified); - - - /** - * Prepare the metadata information to associate with the - * resulting DICOM instance (AnonymizedFrom/ModifiedFrom). - **/ - - DicomInstanceHasher modifiedHasher = modified->GetHasher(); - - if (originalHasher.HashSeries() != modifiedHasher.HashSeries()) - { - toStore.AddMetadata(ResourceType_Series, metadataType, originalHasher.HashSeries()); - } - - if (originalHasher.HashStudy() != modifiedHasher.HashStudy()) - { - toStore.AddMetadata(ResourceType_Study, metadataType, originalHasher.HashStudy()); - } - - if (originalHasher.HashPatient() != modifiedHasher.HashPatient()) - { - toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher.HashPatient()); - } - - assert(*it == originalHasher.HashInstance()); - toStore.AddMetadata(ResourceType_Instance, metadataType, *it); - - - /** - * Store the resulting DICOM instance into the Orthanc store. - **/ - - std::string modifiedInstance; - if (context.Store(modifiedInstance, toStore) != StoreStatus_Success) - { - LOG(ERROR) << "Error while storing a modified instance " << *it; - throw OrthancException(ErrorCode_CannotStoreInstance); - } - - // Sanity checks in debug mode - assert(modifiedInstance == modifiedHasher.HashInstance()); - - - /** - * Compute the JSON object that is returned by the REST call. - **/ - - if (isFirst) - { - std::string newId; - - switch (resourceType) - { - case ResourceType_Series: - newId = modifiedHasher.HashSeries(); - break; - - case ResourceType_Study: - newId = modifiedHasher.HashStudy(); - break; - - case ResourceType_Patient: - newId = modifiedHasher.HashPatient(); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - result["Type"] = EnumerationToString(resourceType); - result["ID"] = newId; - result["Path"] = GetBasePath(resourceType, newId); - result["PatientID"] = modifiedHasher.HashPatient(); - isFirst = false; - } - } - - call.GetOutput().AnswerJson(result); - } - - - static void ModifyInstance(RestApiPostCall& call) { DicomModification modification; modification.SetAllowManualIdentifiers(true); - if (ParseModifyRequest(modification, call)) + Json::Value request; + ParseModifyRequest(request, modification, call); + + if (modification.IsReplaced(DICOM_TAG_PATIENT_ID)) + { + modification.SetLevel(ResourceType_Patient); + } + else if (modification.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) { - if (modification.IsReplaced(DICOM_TAG_PATIENT_ID)) - { - modification.SetLevel(ResourceType_Patient); - } - else if (modification.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - modification.SetLevel(ResourceType_Study); - } - else if (modification.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - modification.SetLevel(ResourceType_Series); - } - else - { - modification.SetLevel(ResourceType_Instance); - } + modification.SetLevel(ResourceType_Study); + } + else if (modification.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + modification.SetLevel(ResourceType_Series); + } + else + { + modification.SetLevel(ResourceType_Instance); + } - AnonymizeOrModifyInstance(modification, call); - } + AnonymizeOrModifyInstance(modification, call); } @@ -398,39 +151,58 @@ DicomModification modification; modification.SetAllowManualIdentifiers(true); - if (ParseAnonymizationRequest(modification, call)) - { - AnonymizeOrModifyInstance(modification, call); - } + Json::Value request; + ParseAnonymizationRequest(request, modification, call); + + AnonymizeOrModifyInstance(modification, call); } - template <enum ChangeType changeType, - enum ResourceType resourceType> - static void ModifyResource(RestApiPostCall& call) + static void SubmitModificationJob(std::auto_ptr<DicomModification>& modification, + bool isAnonymization, + RestApiPostCall& call, + const Json::Value& body, + ResourceType level) { - DicomModification modification; + ServerContext& context = OrthancRestApi::GetContext(call); - if (ParseModifyRequest(modification, call)) - { - modification.SetLevel(resourceType); - AnonymizeOrModifyResource(modification, MetadataType_ModifiedFrom, - changeType, resourceType, call); - } + std::auto_ptr<ResourceModificationJob> job(new ResourceModificationJob(context)); + + job->SetModification(modification.release(), level, isAnonymization); + job->SetOrigin(call); + + context.AddChildInstances(*job, call.GetUriComponent("id", "")); + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, body); } - template <enum ChangeType changeType, - enum ResourceType resourceType> + template <enum ResourceType resourceType> + static void ModifyResource(RestApiPostCall& call) + { + std::auto_ptr<DicomModification> modification(new DicomModification); + + Json::Value body; + ParseModifyRequest(body, *modification, call); + + modification->SetLevel(resourceType); + + SubmitModificationJob(modification, false /* not an anonymization */, + call, body, resourceType); + } + + + template <enum ResourceType resourceType> static void AnonymizeResource(RestApiPostCall& call) { - DicomModification modification; + std::auto_ptr<DicomModification> modification(new DicomModification); - if (ParseAnonymizationRequest(modification, call)) - { - AnonymizeOrModifyResource(modification, MetadataType_AnonymizedFrom, - changeType, resourceType, call); - } + Json::Value body; + ParseAnonymizationRequest(body, *modification, call); + + SubmitModificationJob(modification, true /* anonymization */, + call, body, resourceType); } @@ -439,7 +211,7 @@ ParsedDicomFile& dicom) { DicomInstanceToStore toStore; - toStore.SetRestOrigin(call); + toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); toStore.SetParsedDicomFile(dicom); ServerContext& context = OrthancRestApi::GetContext(call); @@ -480,7 +252,8 @@ } else { - dicom.Replace(tag, value); + // This is V1, don't try and decode data URI scheme + dicom.ReplacePlainString(tag, value); } } } @@ -526,7 +299,7 @@ } else { - dicom.Replace(tag, tags[name], decodeBinaryTags); + dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent); } } } @@ -542,8 +315,8 @@ assert(content.size() > 0); ServerContext& context = OrthancRestApi::GetContext(call); - base.Replace(DICOM_TAG_IMAGES_IN_ACQUISITION, boost::lexical_cast<std::string>(content.size())); - base.Replace(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "1"); + base.ReplacePlainString(DICOM_TAG_IMAGES_IN_ACQUISITION, boost::lexical_cast<std::string>(content.size())); + base.ReplacePlainString(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "1"); std::string someInstance; @@ -551,7 +324,7 @@ { for (Json::ArrayIndex i = 0; i < content.size(); i++) { - std::auto_ptr<ParsedDicomFile> dicom(base.Clone()); + std::auto_ptr<ParsedDicomFile> dicom(base.Clone(false)); const Json::Value* payload = NULL; if (content[i].type() == Json::stringValue) @@ -580,8 +353,8 @@ } dicom->EmbedContent(payload->asString()); - dicom->Replace(DICOM_TAG_INSTANCE_NUMBER, boost::lexical_cast<std::string>(i + 1)); - dicom->Replace(DICOM_TAG_IMAGE_INDEX, boost::lexical_cast<std::string>(i + 1)); + dicom->ReplacePlainString(DICOM_TAG_INSTANCE_NUMBER, boost::lexical_cast<std::string>(i + 1)); + dicom->ReplacePlainString(DICOM_TAG_IMAGE_INDEX, boost::lexical_cast<std::string>(i + 1)); StoreCreatedInstance(someInstance, call, *dicom); } @@ -620,7 +393,7 @@ throw OrthancException(ErrorCode_BadRequest); } - ParsedDicomFile dicom; + ParsedDicomFile dicom(true); { Encoding encoding; @@ -636,8 +409,7 @@ } else { - std::string tmp = Configuration::GetGlobalStringParameter("DefaultEncoding", "Latin1"); - encoding = StringToEncoding(tmp.c_str()); + encoding = GetDefaultDicomEncoding(); } dicom.SetEncoding(encoding); @@ -674,7 +446,7 @@ throw OrthancException(ErrorCode_InternalError); } - context.ReadJson(siblingTags, siblingInstances.front()); + context.ReadDicomAsJson(siblingTags, siblingInstances.front()); } @@ -731,12 +503,12 @@ const Json::Value& tag = siblingTags[t]; if (tag["Type"] == "Null") { - dicom.Replace(*it, ""); + dicom.ReplacePlainString(*it, ""); } else if (tag["Type"] == "String") { std::string value = tag["Value"].asString(); - dicom.Replace(*it, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding())); + dicom.ReplacePlainString(*it, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding())); } } } @@ -758,27 +530,27 @@ // Inject time-related information std::string date, time; - Toolbox::GetNowDicom(date, time); - dicom.Replace(DICOM_TAG_ACQUISITION_DATE, date); - dicom.Replace(DICOM_TAG_ACQUISITION_TIME, time); - dicom.Replace(DICOM_TAG_CONTENT_DATE, date); - dicom.Replace(DICOM_TAG_CONTENT_TIME, time); - dicom.Replace(DICOM_TAG_INSTANCE_CREATION_DATE, date); - dicom.Replace(DICOM_TAG_INSTANCE_CREATION_TIME, time); + SystemToolbox::GetNowDicom(date, time, true /* use UTC time (not local time) */); + dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_TIME, time); + dicom.ReplacePlainString(DICOM_TAG_CONTENT_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_CONTENT_TIME, time); + dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_TIME, time); if (parentType == ResourceType_Patient || parentType == ResourceType_Study || parentType == ResourceType_Instance /* no parent */) { - dicom.Replace(DICOM_TAG_SERIES_DATE, date); - dicom.Replace(DICOM_TAG_SERIES_TIME, time); + dicom.ReplacePlainString(DICOM_TAG_SERIES_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_SERIES_TIME, time); } if (parentType == ResourceType_Patient || parentType == ResourceType_Instance /* no parent */) { - dicom.Replace(DICOM_TAG_STUDY_DATE, date); - dicom.Replace(DICOM_TAG_STUDY_TIME, time); + dicom.ReplacePlainString(DICOM_TAG_STUDY_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_STUDY_TIME, time); } @@ -834,7 +606,7 @@ else { // Compatibility with Orthanc <= 0.9.3 - ParsedDicomFile dicom; + ParsedDicomFile dicom(true); CreateDicomV1(dicom, call, request); std::string id; @@ -844,18 +616,141 @@ } + static void SplitStudy(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value request; + if (!call.ParseJsonRequest(request)) + { + // Bad JSON request + throw OrthancException(ErrorCode_BadFileFormat); + } + + const std::string study = call.GetUriComponent("id", ""); + + std::auto_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); + job->SetOrigin(call); + + std::vector<std::string> series; + SerializationToolbox::ReadArrayOfStrings(series, request, "Series"); + + for (size_t i = 0; i < series.size(); i++) + { + job->AddSourceSeries(series[i]); + } + + job->AddTrailingStep(); + + static const char* KEEP_SOURCE = "KeepSource"; + if (request.isMember(KEEP_SOURCE)) + { + job->SetKeepSource(SerializationToolbox::ReadBoolean(request, KEEP_SOURCE)); + } + + static const char* REMOVE = "Remove"; + if (request.isMember(REMOVE)) + { + if (request[REMOVE].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + for (Json::Value::ArrayIndex i = 0; i < request[REMOVE].size(); i++) + { + if (request[REMOVE][i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + job->Remove(FromDcmtkBridge::ParseTag(request[REMOVE][i].asCString())); + } + } + } + + static const char* REPLACE = "Replace"; + if (request.isMember(REPLACE)) + { + if (request[REPLACE].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members tags = request[REPLACE].getMemberNames(); + + for (size_t i = 0; i < tags.size(); i++) + { + const Json::Value& value = request[REPLACE][tags[i]]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + job->Replace(FromDcmtkBridge::ParseTag(tags[i]), value.asString()); + } + } + } + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, request); + } + + + static void MergeStudy(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value request; + if (!call.ParseJsonRequest(request)) + { + // Bad JSON request + throw OrthancException(ErrorCode_BadFileFormat); + } + + const std::string study = call.GetUriComponent("id", ""); + + std::auto_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); + job->SetOrigin(call); + + std::vector<std::string> resources; + SerializationToolbox::ReadArrayOfStrings(resources, request, "Resources"); + + for (size_t i = 0; i < resources.size(); i++) + { + job->AddSource(resources[i]); + } + + job->AddTrailingStep(); + + static const char* KEEP_SOURCE = "KeepSource"; + if (request.isMember(KEEP_SOURCE)) + { + job->SetKeepSource(SerializationToolbox::ReadBoolean(request, KEEP_SOURCE)); + } + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, request); + } + + void OrthancRestApi::RegisterAnonymizeModify() { Register("/instances/{id}/modify", ModifyInstance); - Register("/series/{id}/modify", ModifyResource<ChangeType_ModifiedSeries, ResourceType_Series>); - Register("/studies/{id}/modify", ModifyResource<ChangeType_ModifiedStudy, ResourceType_Study>); - Register("/patients/{id}/modify", ModifyResource<ChangeType_ModifiedPatient, ResourceType_Patient>); + Register("/series/{id}/modify", ModifyResource<ResourceType_Series>); + Register("/studies/{id}/modify", ModifyResource<ResourceType_Study>); + Register("/patients/{id}/modify", ModifyResource<ResourceType_Patient>); Register("/instances/{id}/anonymize", AnonymizeInstance); - Register("/series/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedSeries, ResourceType_Series>); - Register("/studies/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedStudy, ResourceType_Study>); - Register("/patients/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedPatient, ResourceType_Patient>); + Register("/series/{id}/anonymize", AnonymizeResource<ResourceType_Series>); + Register("/studies/{id}/anonymize", AnonymizeResource<ResourceType_Study>); + Register("/patients/{id}/anonymize", AnonymizeResource<ResourceType_Patient>); Register("/tools/create-dicom", CreateDicom); + + Register("/studies/{id}/split", SplitStudy); + Register("/studies/{id}/merge", MergeStudy); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +35,7 @@ #include "OrthancRestApi.h" #include "../../Core/Logging.h" -#include "../DicomModification.h" +#include "../../Core/SerializationToolbox.h" #include "../ServerContext.h" namespace Orthanc @@ -93,7 +94,7 @@ std::string postData(call.GetBodyData(), call.GetBodySize()); DicomInstanceToStore toStore; - toStore.SetRestOrigin(call); + toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); toStore.SetBuffer(postData); std::string publicId; @@ -139,4 +140,102 @@ { return GetContext(call).GetIndex(); } + + + + static const char* KEY_PERMISSIVE = "Permissive"; + static const char* KEY_PRIORITY = "Priority"; + static const char* KEY_SYNCHRONOUS = "Synchronous"; + static const char* KEY_ASYNCHRONOUS = "Asynchronous"; + + void OrthancRestApi::SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous, + const Json::Value& body) const + { + std::auto_ptr<SetOfCommandsJob> raii(job); + + if (job == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + if (body.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + job->SetDescription("REST API"); + + if (body.isMember(KEY_PERMISSIVE)) + { + job->SetPermissive(SerializationToolbox::ReadBoolean(body, KEY_PERMISSIVE)); + } + else + { + job->SetPermissive(false); + } + + int priority = 0; + + if (body.isMember(KEY_PRIORITY)) + { + priority = SerializationToolbox::ReadInteger(body, KEY_PRIORITY); + } + + bool synchronous = isDefaultSynchronous; + + if (body.isMember(KEY_SYNCHRONOUS)) + { + synchronous = SerializationToolbox::ReadBoolean(body, KEY_SYNCHRONOUS); + } + else if (body.isMember(KEY_ASYNCHRONOUS)) + { + synchronous = !SerializationToolbox::ReadBoolean(body, KEY_ASYNCHRONOUS); + } + + if (synchronous) + { + Json::Value successContent; + if (context_.GetJobsEngine().GetRegistry().SubmitAndWait + (successContent, raii.release(), priority)) + { + // Success in synchronous execution + call.GetOutput().AnswerJson(successContent); + } + else + { + // Error during synchronous execution + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + } + } + else + { + // Asynchronous mode: Submit the job, but don't wait for its completion + std::string id; + context_.GetJobsEngine().GetRegistry().Submit(id, raii.release(), priority); + + Json::Value v; + v["ID"] = id; + v["Path"] = "/jobs/" + id; + call.GetOutput().AnswerJson(v); + } + } + + + void OrthancRestApi::SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous) const + { + std::auto_ptr<SetOfCommandsJob> raii(job); + + Json::Value body; + + if (!call.ParseJsonRequest(body)) + { + body = Json::objectValue; + } + + SubmitCommandsJob(call, raii.release(), isDefaultSynchronous, body); + } }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,8 +33,10 @@ #pragma once +#include "../../Core/JobsEngine/SetOfCommandsJob.h" #include "../../Core/RestApi/RestApi.h" -#include "../DicomModification.h" +#include "../../Core/DicomParsing/DicomModification.h" +#include "../ServerEnumerations.h" #include <set> @@ -95,7 +98,13 @@ ResourceType resourceType, StoreStatus status) const; - static bool ParseModifyRequest(DicomModification& target, - const Json::Value& request); + void SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous, + const Json::Value& body) const; + + void SubmitCommandsJob(RestApiPostCall& call, + SetOfCommandsJob* job, + bool isDefaultSynchronous) const; }; }
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,349 +34,138 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include "../DicomDirWriter.h" -#include "../../Core/Compression/HierarchicalZipWriter.h" #include "../../Core/HttpServer/FilesystemHttpSender.h" -#include "../../Core/Logging.h" -#include "../../Core/Uuid.h" -#include "../ServerContext.h" - -#include <stdio.h> - -#if defined(_MSC_VER) -#define snprintf _snprintf -#endif - -static const uint64_t MEGA_BYTES = 1024 * 1024; -static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; +#include "../ServerJobs/ArchiveJob.h" namespace Orthanc { - // Download of ZIP files ---------------------------------------------------- - - static std::string GetDirectoryNameInArchive(const Json::Value& resource, - ResourceType resourceType) + static bool AddResourcesOfInterest(ArchiveJob& job, + RestApiPostCall& call) { - std::string s; - const Json::Value& tags = resource["MainDicomTags"]; - - switch (resourceType) + Json::Value resources; + if (call.ParseJsonRequest(resources) && + resources.type() == Json::arrayValue) { - case ResourceType_Patient: + for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++) { - std::string p = tags["PatientID"].asString(); - std::string n = tags["PatientName"].asString(); - s = p + " " + n; - break; - } - - case ResourceType_Study: - { - std::string p; - if (tags.isMember("AccessionNumber")) + if (resources[i].type() != Json::stringValue) { - p = tags["AccessionNumber"].asString() + " "; + return false; // Bad request } - s = p + tags["StudyDescription"].asString(); - break; - } - - case ResourceType_Series: - { - std::string d = tags["SeriesDescription"].asString(); - std::string m = tags["Modality"].asString(); - s = m + " " + d; - break; + job.AddResource(resources[i].asString()); } - - default: - throw OrthancException(ErrorCode_InternalError); - } - // Get rid of special characters - return Toolbox::ConvertToAscii(s); - } - - static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, - ServerContext& context, - const Json::Value& resource, - ResourceType resourceType) - { - if (resourceType == ResourceType_Patient) - { return true; } - - ResourceType parentType = GetParentResourceType(resourceType); - Json::Value parent; - - switch (resourceType) - { - case ResourceType_Study: - { - if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType)) - { - return false; - } - - break; - } - - case ResourceType_Series: - if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) || - !CreateRootDirectoryInArchive(writer, context, parent, parentType)) - { - return false; - } - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str()); - return true; - } - - static bool ArchiveInstance(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& instancePublicId, - const char* filename) - { - writer.OpenFile(filename); - - std::string dicom; - context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); - writer.Write(dicom); - - return true; - } - - static bool ArchiveInternal(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& publicId, - ResourceType resourceType, - bool isFirstLevel) - { - Json::Value resource; - if (!context.GetIndex().LookupResource(resource, publicId, resourceType)) - { - return false; - } - - if (isFirstLevel && - !CreateRootDirectoryInArchive(writer, context, resource, resourceType)) + else { return false; } - - writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str()); - - switch (resourceType) - { - case ResourceType_Patient: - for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++) - { - std::string studyId = resource["Studies"][i].asString(); - if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false)) - { - return false; - } - } - break; + } - case ResourceType_Study: - for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++) - { - std::string seriesId = resource["Series"][i].asString(); - if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false)) - { - return false; - } - } - break; - - case ResourceType_Series: - { - // Create a filename prefix, depending on the modality - char format[24] = "%08d.dcm"; - if (resource["MainDicomTags"].isMember("Modality")) - { - std::string modality = resource["MainDicomTags"]["Modality"].asString(); - - if (modality.size() == 1) - { - snprintf(format, sizeof(format) - 1, "%c%%07d.dcm", toupper(modality[0])); - } - else if (modality.size() >= 2) - { - snprintf(format, sizeof(format) - 1, "%c%c%%06d.dcm", toupper(modality[0]), toupper(modality[1])); - } - } - - char filename[24]; - - for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) - { - snprintf(filename, sizeof(filename) - 1, format, i); - - std::string publicId = resource["Instances"][i].asString(); - - // This was the implementation up to Orthanc 0.7.0: - // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm"; - - if (!ArchiveInstance(writer, context, publicId, filename)) - { - return false; - } - } - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); + static void SubmitJob(RestApiCall& call, + boost::shared_ptr<TemporaryFile>& tmp, + ServerContext& context, + std::auto_ptr<ArchiveJob>& job, + const std::string& filename) + { + if (job.get() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); } - writer.CloseDirectory(); - return true; - } + job->SetDescription("REST API"); + Json::Value publicContent; + if (context.GetJobsEngine().GetRegistry().SubmitAndWait + (publicContent, job.release(), 0 /* TODO priority */)) + { + // The archive is now created: Prepare the sending of the ZIP file + FilesystemHttpSender sender(tmp->GetPath()); + sender.SetContentType("application/zip"); + sender.SetContentFilename(filename); - static bool IsZip64Required(ServerIndex& index, - const std::string& id) + // Send the ZIP + call.GetOutput().AnswerStream(sender); + } + else + { + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + } + } + + + static void CreateBatchArchive(RestApiPostCall& call) { - /** - * Determine whether ZIP64 is required. Original ZIP format can - * store up to 2GB of data (some implementation supporting up to - * 4GB of data), and up to 65535 files. - * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 - **/ + ServerContext& context = OrthancRestApi::GetContext(call); - uint64_t uncompressedSize; - uint64_t compressedSize; - unsigned int countStudies; - unsigned int countSeries; - unsigned int countInstances; - index.GetStatistics(compressedSize, uncompressedSize, - countStudies, countSeries, countInstances, id); - const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || - countInstances >= 65535); + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false)); + + if (AddResourcesOfInterest(*job, call)) + { + SubmitJob(call, tmp, context, job, "Archive.zip"); + } + } - LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " - << (uncompressedSize / MEGA_BYTES) << "MB using the " - << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; + + template <bool Extended> + static void CreateBatchMedia(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, Extended)); - return isZip64; + if (AddResourcesOfInterest(*job, call)) + { + SubmitJob(call, tmp, context, job, "Archive.zip"); + } } - + - template <enum ResourceType resourceType> - static void GetArchive(RestApiGetCall& call) + static void CreateArchive(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string id = call.GetUriComponent("id", ""); - bool isZip64 = IsZip64Required(context.GetIndex(), id); - - // Create a RAII for the temporary file to manage the ZIP file - Toolbox::TemporaryFile tmp; - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - writer.SetZip64(isZip64); - // Store the requested resource into the ZIP - if (!ArchiveInternal(writer, context, id, resourceType, true)) - { - return; - } - } + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false)); + job->AddResource(id); - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath()); - sender.SetContentType("application/zip"); - sender.SetContentFilename(id + ".zip"); - - // Send the ZIP - call.GetOutput().AnswerStream(sender); - - // The temporary file is automatically removed thanks to the RAII + SubmitJob(call, tmp, context, job, id + ".zip"); } - static void GetMediaArchive(RestApiGetCall& call) + static void CreateMedia(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string id = call.GetUriComponent("id", ""); - bool isZip64 = IsZip64Required(context.GetIndex(), id); - - // Create a RAII for the temporary file to manage the ZIP file - Toolbox::TemporaryFile tmp; - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - writer.SetZip64(isZip64); - writer.OpenDirectory("IMAGES"); - - // Create the DICOMDIR writer - DicomDirWriter dicomDir; - - // Retrieve the list of the instances - std::list<std::string> instances; - context.GetIndex().GetChildInstances(instances, id); - size_t pos = 0; - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it, ++pos) - { - // "DICOM restricts the filenames on DICOM media to 8 - // characters (some systems wrongly use 8.3, but this does not - // conform to the standard)." - std::string filename = "IM" + boost::lexical_cast<std::string>(pos); - writer.OpenFile(filename.c_str()); - - std::string dicom; - context.ReadFile(dicom, *it, FileContentType_Dicom); - writer.Write(dicom); - - ParsedDicomFile parsed(dicom); - dicomDir.Add("IMAGES", filename, parsed); - } + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, call.HasArgument("extended"))); + job->AddResource(id); - // Add the DICOMDIR - writer.CloseDirectory(); - writer.OpenFile("DICOMDIR"); - std::string s; - dicomDir.Encode(s); - writer.Write(s); - } - - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath()); - sender.SetContentType("application/zip"); - sender.SetContentFilename(id + ".zip"); - - // Send the ZIP - call.GetOutput().AnswerStream(sender); - - // The temporary file is automatically removed thanks to the RAII + SubmitJob(call, tmp, context, job, id + ".zip"); } void OrthancRestApi::RegisterArchive() { - Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); - Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); - Register("/series/{id}/archive", GetArchive<ResourceType_Series>); + Register("/patients/{id}/archive", CreateArchive); + Register("/studies/{id}/archive", CreateArchive); + Register("/series/{id}/archive", CreateArchive); - Register("/patients/{id}/media", GetMediaArchive); - Register("/studies/{id}/media", GetMediaArchive); - Register("/series/{id}/media", GetMediaArchive); + Register("/patients/{id}/media", CreateMedia); + Register("/studies/{id}/media", CreateMedia); + Register("/series/{id}/media", CreateMedia); + + Register("/tools/create-archive", CreateBatchArchive); + Register("/tools/create-media", CreateBatchMedia<false>); + Register("/tools/create-media-extended", CreateBatchMedia<true>); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +60,7 @@ since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); } - catch (boost::bad_lexical_cast) + catch (boost::bad_lexical_cast&) { return; }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,16 +34,17 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include "../OrthancInitialization.h" -#include "../../Core/HttpClient.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/Logging.h" -#include "../FromDcmtkBridge.h" -#include "../Scheduler/ServerJob.h" -#include "../Scheduler/StoreScuCommand.h" -#include "../Scheduler/StorePeerCommand.h" +#include "../../Core/SerializationToolbox.h" +#include "../OrthancInitialization.h" #include "../QueryRetrieveHandler.h" +#include "../ServerJobs/DicomModalityStoreJob.h" +#include "../ServerJobs/DicomMoveScuJob.h" +#include "../ServerJobs/OrthancPeerStoreJob.h" #include "../ServerToolbox.h" + namespace Orthanc { /*************************************************************************** @@ -54,12 +56,15 @@ ServerContext& context = OrthancRestApi::GetContext(call); const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); try { - if (locker.GetConnection().Echo()) + DicomUserConnection connection(localAet, remote); + connection.Open(); + + if (connection.Echo()) { // Echo has succeeded call.GetOutput().AnswerBuffer("{}", "application/json"); @@ -97,7 +102,7 @@ for (size_t i = 0; i < members.size(); i++) { DicomTag t = FromDcmtkBridge::ParseTag(members[i]); - result.SetValue(t, query[members[i]].asString()); + result.SetValue(t, query[members[i]].asString(), false); } return true; @@ -175,11 +180,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + DicomFindAnswers answers(false); - DicomFindAnswers answers; - FindPatient(answers, locker.GetConnection(), fields); + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindPatient(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -205,11 +215,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + DicomFindAnswers answers(false); - DicomFindAnswers answers; - FindStudy(answers, locker.GetConnection(), fields); + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindStudy(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -236,11 +251,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + DicomFindAnswers answers(false); - DicomFindAnswers answers; - FindSeries(answers, locker.GetConnection(), fields); + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindSeries(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -268,11 +288,16 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + DicomFindAnswers answers(false); - DicomFindAnswers answers; - FindInstance(answers, locker.GetConnection(), fields); + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + FindInstance(answers, connection, fields); + } Json::Value result; answers.ToJson(result, true); @@ -280,6 +305,18 @@ } + static void CopyTagIfExists(DicomMap& target, + ParsedDicomFile& source, + const DicomTag& tag) + { + std::string tmp; + if (source.GetTagValue(tmp, tag)) + { + target.SetValue(tag, tmp, false); + } + } + + static void DicomFind(RestApiPostCall& call) { LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); @@ -293,54 +330,59 @@ } const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); - RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - DicomFindAnswers patients; - FindPatient(patients, locker.GetConnection(), m); + DicomUserConnection connection(localAet, remote); + connection.Open(); + + DicomFindAnswers patients(false); + FindPatient(patients, connection, m); // Loop over the found patients Json::Value result = Json::arrayValue; for (size_t i = 0; i < patients.GetSize(); i++) { - Json::Value patient(Json::objectValue); - FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i), true); + Json::Value patient; + patients.ToJson(patient, i, true); DicomMap::SetupFindStudyTemplate(m); if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize())) { return; } - m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); + + CopyTagIfExists(m, patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); - DicomFindAnswers studies; - FindStudy(studies, locker.GetConnection(), m); + DicomFindAnswers studies(false); + FindStudy(studies, connection, m); patient["Studies"] = Json::arrayValue; // Loop over the found studies for (size_t j = 0; j < studies.GetSize(); j++) { - Json::Value study(Json::objectValue); - FromDcmtkBridge::ToJson(study, studies.GetAnswer(j), true); + Json::Value study; + studies.ToJson(study, j, true); DicomMap::SetupFindSeriesTemplate(m); if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize())) { return; } - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); - DicomFindAnswers series; - FindSeries(series, locker.GetConnection(), m); + CopyTagIfExists(m, studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); + CopyTagIfExists(m, studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); + + DicomFindAnswers series(false); + FindSeries(series, connection, m); // Loop over the found series study["Series"] = Json::arrayValue; for (size_t k = 0; k < series.GetSize(); k++) { - Json::Value series2(Json::objectValue); - FromDcmtkBridge::ToJson(series2, series.GetAnswer(k), true); + Json::Value series2; + series.ToJson(series2, k, true); study["Series"].append(series2); } @@ -372,7 +414,7 @@ std::auto_ptr<QueryRetrieveHandler> handler(new QueryRetrieveHandler(context)); handler->SetModality(call.GetUriComponent("id", "")); - handler->SetLevel(StringToResourceType(request["Level"].asString().c_str())); + handler->SetLevel(StringToResourceType(request["Level"].asCString())); if (request.isMember("Query")) { @@ -390,7 +432,7 @@ Json::Value result = Json::objectValue; result["ID"] = s; result["Path"] = "/queries/" + s; - call.GetOutput().AnswerJson(result); + call.GetOutput().AnswerJson(result); } } @@ -430,9 +472,9 @@ { } - QueryRetrieveHandler* operator->() + QueryRetrieveHandler& GetHandler() const { - return &handler_; + return handler_; } }; @@ -450,7 +492,7 @@ static void ListQueryAnswers(RestApiGetCall& call) { QueryAccessor query(call); - size_t count = query->GetAnswerCount(); + size_t count = query.GetHandler().GetAnswersCount(); Json::Value result = Json::arrayValue; for (size_t i = 0; i < count; i++) @@ -465,61 +507,96 @@ static void GetQueryOneAnswer(RestApiGetCall& call) { size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); + QueryAccessor query(call); - AnswerDicomMap(call, query->GetAnswer(index), call.HasArgument("simplify")); + + DicomMap map; + query.GetHandler().GetAnswer(map, index); + + AnswerDicomMap(call, map, call.HasArgument("simplify")); } + static void SubmitRetrieveJob(RestApiPostCall& call, + bool allAnswers, + size_t index) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string targetAet; + + Json::Value body; + if (call.ParseJsonRequest(body)) + { + targetAet = SerializationToolbox::ReadString(body, "TargetAet"); + } + else + { + body = Json::objectValue; + call.BodyToString(targetAet); + } + + std::auto_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context)); + + { + QueryAccessor query(call); + job->SetTargetAet(targetAet); + job->SetLocalAet(query.GetHandler().GetLocalAet()); + job->SetRemoteModality(query.GetHandler().GetRemoteModality()); + + LOG(WARNING) << "Driving C-Move SCU on remote modality " + << query.GetHandler().GetRemoteModality().GetApplicationEntityTitle() + << " to target modality " << targetAet; + + if (allAnswers) + { + for (size_t i = 0; i < query.GetHandler().GetAnswersCount(); i++) + { + job->AddFindAnswer(query.GetHandler(), i); + } + } + else + { + job->AddFindAnswer(query.GetHandler(), index); + } + } + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, body); + } + + static void RetrieveOneAnswer(RestApiPostCall& call) { size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); - - std::string modality; - call.BodyToString(modality); - - LOG(WARNING) << "Driving C-Move SCU on modality: " << modality; - - QueryAccessor query(call); - query->Retrieve(modality, index); - - // Retrieve has succeeded - call.GetOutput().AnswerBuffer("{}", "application/json"); + SubmitRetrieveJob(call, false, index); } static void RetrieveAllAnswers(RestApiPostCall& call) { - std::string modality; - call.BodyToString(modality); - - LOG(WARNING) << "Driving C-Move SCU on modality: " << modality; - - QueryAccessor query(call); - query->Retrieve(modality); - - // Retrieve has succeeded - call.GetOutput().AnswerBuffer("{}", "application/json"); + SubmitRetrieveJob(call, true, 0); } static void GetQueryArguments(RestApiGetCall& call) { QueryAccessor query(call); - AnswerDicomMap(call, query->GetQuery(), call.HasArgument("simplify")); + AnswerDicomMap(call, query.GetHandler().GetQuery(), call.HasArgument("simplify")); } static void GetQueryLevel(RestApiGetCall& call) { QueryAccessor query(call); - call.GetOutput().AnswerBuffer(EnumerationToString(query->GetLevel()), "text/plain"); + call.GetOutput().AnswerBuffer(EnumerationToString(query.GetHandler().GetLevel()), "text/plain"); } static void GetQueryModality(RestApiGetCall& call) { QueryAccessor query(call); - call.GetOutput().AnswerBuffer(query->GetModalitySymbolicName(), "text/plain"); + call.GetOutput().AnswerBuffer(query.GetHandler().GetModalitySymbolicName(), "text/plain"); } @@ -547,7 +624,9 @@ // Ensure that the answer of interest does exist size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); - query->GetAnswer(index); + + DicomMap map; + query.GetHandler().GetAnswer(map, index); RestApi::AutoListChildren(call); } @@ -560,7 +639,7 @@ ***************************************************************************/ static bool GetInstancesToExport(Json::Value& otherArguments, - std::list<std::string>& instances, + SetOfInstancesJob& job, const std::string& remote, RestApiPostCall& call) { @@ -630,19 +709,12 @@ return false; } - if (Configuration::GetGlobalBoolParameter("LogExportedResources", true)) + if (Configuration::GetGlobalBoolParameter("LogExportedResources", false)) { context.GetIndex().LogExportedResource(stripped, remote); } - - std::list<std::string> tmp; - context.GetIndex().GetChildInstances(tmp, stripped); - for (std::list<std::string>::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - instances.push_back(*it); - } + context.AddChildInstances(job, stripped); } return true; @@ -656,39 +728,79 @@ std::string remote = call.GetUriComponent("id", ""); Json::Value request; - std::list<std::string> instances; - if (!GetInstancesToExport(request, instances, remote, call)) + std::auto_ptr<DicomModalityStoreJob> job(new DicomModalityStoreJob(context)); + + if (GetInstancesToExport(request, *job, remote, call)) { - return; + std::string localAet = Toolbox::GetJsonStringField + (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); + std::string moveOriginatorAET = Toolbox::GetJsonStringField + (request, "MoveOriginatorAet", context.GetDefaultLocalApplicationEntityTitle()); + int moveOriginatorID = Toolbox::GetJsonIntegerField + (request, "MoveOriginatorID", 0 /* By default, not a C-MOVE */); + + job->SetLocalAet(localAet); + job->SetRemoteModality(Configuration::GetModalityUsingSymbolicName(remote)); + + if (moveOriginatorID != 0) + { + job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID); + } + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, request); } + } - std::string localAet = context.GetDefaultLocalApplicationEntityTitle(); - if (request.isMember("LocalAet")) + + /*************************************************************************** + * DICOM C-Move SCU + ***************************************************************************/ + + static void DicomMove(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value request; + + static const char* RESOURCES = "Resources"; + static const char* LEVEL = "Level"; + + if (!call.ParseJsonRequest(request) || + request.type() != Json::objectValue || + !request.isMember(RESOURCES) || + !request.isMember(LEVEL) || + request[RESOURCES].type() != Json::arrayValue || + request[LEVEL].type() != Json::stringValue) { - localAet = request["LocalAet"].asString(); + throw OrthancException(ErrorCode_BadFileFormat); } - RemoteModalityParameters p = Configuration::GetModalityUsingSymbolicName(remote); + ResourceType level = StringToResourceType(request["Level"].asCString()); + + std::string localAet = Toolbox::GetJsonStringField + (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle()); + std::string targetAet = Toolbox::GetJsonStringField + (request, "TargetAet", context.GetDefaultLocalApplicationEntityTitle()); + + const RemoteModalityParameters source = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ServerJob job; - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) + DicomUserConnection connection(localAet, source); + connection.Open(); + + for (Json::Value::ArrayIndex i = 0; i < request[RESOURCES].size(); i++) { - job.AddCommand(new StoreScuCommand(context, localAet, p, false)).AddInput(*it); + DicomMap resource; + FromDcmtkBridge::FromJson(resource, request[RESOURCES][i]); + + connection.Move(targetAet, level, resource); } - job.SetDescription("HTTP request: Store-SCU to peer \"" + remote + "\""); + // Move has succeeded + call.GetOutput().AnswerBuffer("{}", "application/json"); + } - if (context.GetScheduler().SubmitAndWait(job)) - { - // Success - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else - { - call.GetOutput().SignalError(HttpStatus_500_InternalServerError); - } - } /*************************************************************************** @@ -706,14 +818,41 @@ OrthancRestApi::SetOfStrings peers; Configuration::GetListOfOrthancPeers(peers); - Json::Value result = Json::arrayValue; - for (OrthancRestApi::SetOfStrings::const_iterator - it = peers.begin(); it != peers.end(); ++it) + if (call.HasArgument("expand")) { - result.append(*it); + Json::Value result = Json::objectValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = peers.begin(); it != peers.end(); ++it) + { + WebServiceParameters peer; + + if (Configuration::GetOrthancPeer(peer, *it)) + { + Json::Value jsonPeer = Json::objectValue; + // only return the minimum information to identify the + // destination, do not include "security" information like + // passwords + jsonPeer["Url"] = peer.GetUrl(); + if (!peer.GetUsername().empty()) + { + jsonPeer["Username"] = peer.GetUsername(); + } + result[*it] = jsonPeer; + } + } + call.GetOutput().AnswerJson(result); } + else // if expand is not present, keep backward compatibility and return an array of peers + { + Json::Value result = Json::arrayValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = peers.begin(); it != peers.end(); ++it) + { + result.append(*it); + } - call.GetOutput().AnswerJson(result); + call.GetOutput().AnswerJson(result); + } } static void ListPeerOperations(RestApiGetCall& call) @@ -735,32 +874,22 @@ std::string remote = call.GetUriComponent("id", ""); Json::Value request; - std::list<std::string> instances; - if (!GetInstancesToExport(request, instances, remote, call)) - { - return; - } + std::auto_ptr<OrthancPeerStoreJob> job(new OrthancPeerStoreJob(context)); - OrthancPeerParameters peer; - Configuration::GetOrthancPeer(peer, remote); - - ServerJob job; - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) + if (GetInstancesToExport(request, *job, remote, call)) { - job.AddCommand(new StorePeerCommand(context, peer, false)).AddInput(*it); - } - - job.SetDescription("HTTP request: POST to peer \"" + remote + "\""); - - if (context.GetScheduler().SubmitAndWait(job)) - { - // Success - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else - { - call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + WebServiceParameters peer; + if (Configuration::GetOrthancPeer(peer, remote)) + { + job->SetPeer(peer); + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, request); + } + else + { + LOG(ERROR) << "No peer with symbolic name: " << remote; + throw OrthancException(ErrorCode_UnknownResource); + } } } @@ -778,14 +907,30 @@ OrthancRestApi::SetOfStrings modalities; Configuration::GetListOfDicomModalities(modalities); - Json::Value result = Json::arrayValue; - for (OrthancRestApi::SetOfStrings::const_iterator - it = modalities.begin(); it != modalities.end(); ++it) + if (call.HasArgument("expand")) { - result.append(*it); + Json::Value result = Json::objectValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = modalities.begin(); it != modalities.end(); ++it) + { + const RemoteModalityParameters& remote = Configuration::GetModalityUsingSymbolicName(*it); + + Json::Value info; + remote.Serialize(info, true /* force advanced format */); + result[*it] = info; + } + call.GetOutput().AnswerJson(result); } - - call.GetOutput().AnswerJson(result); + else // if expand is not present, keep backward compatibility and return an array of modalities ids + { + Json::Value result = Json::arrayValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = modalities.begin(); it != modalities.end(); ++it) + { + result.append(*it); + } + call.GetOutput().AnswerJson(result); + } } @@ -804,13 +949,15 @@ static void UpdateModality(RestApiPutCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + Json::Value json; Json::Reader reader; if (reader.parse(call.GetBodyData(), call.GetBodyData() + call.GetBodySize(), json)) { RemoteModalityParameters modality; - modality.FromJson(json); - Configuration::UpdateModality(call.GetUriComponent("id", ""), modality); + modality.Unserialize(json); + Configuration::UpdateModality(context, call.GetUriComponent("id", ""), modality); call.GetOutput().AnswerBuffer("", "text/plain"); } } @@ -818,20 +965,24 @@ static void DeleteModality(RestApiDeleteCall& call) { - Configuration::RemoveModality(call.GetUriComponent("id", "")); + ServerContext& context = OrthancRestApi::GetContext(call); + + Configuration::RemoveModality(context, call.GetUriComponent("id", "")); call.GetOutput().AnswerBuffer("", "text/plain"); } static void UpdatePeer(RestApiPutCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + Json::Value json; Json::Reader reader; if (reader.parse(call.GetBodyData(), call.GetBodyData() + call.GetBodySize(), json)) { - OrthancPeerParameters peer; - peer.FromJson(json); - Configuration::UpdatePeer(call.GetUriComponent("id", ""), peer); + WebServiceParameters peer; + peer.Unserialize(json); + Configuration::UpdatePeer(context, call.GetUriComponent("id", ""), peer); call.GetOutput().AnswerBuffer("", "text/plain"); } } @@ -839,11 +990,41 @@ static void DeletePeer(RestApiDeleteCall& call) { - Configuration::RemovePeer(call.GetUriComponent("id", "")); + ServerContext& context = OrthancRestApi::GetContext(call); + + Configuration::RemovePeer(context, call.GetUriComponent("id", "")); call.GetOutput().AnswerBuffer("", "text/plain"); } + static void DicomFindWorklist(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value json; + if (call.ParseJsonRequest(json)) + { + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); + RemoteModalityParameters remote = + Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + + std::auto_ptr<ParsedDicomFile> query(ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0))); + + DicomFindAnswers answers(true); + + { + DicomUserConnection connection(localAet, remote); + connection.Open(); + connection.FindWorklist(answers, *query); + } + + Json::Value result; + answers.ToJson(result, true); + call.GetOutput().AnswerJson(result); + } + } + + void OrthancRestApi::RegisterModalities() { Register("/modalities", ListModalities); @@ -857,6 +1038,7 @@ Register("/modalities/{id}/find-instance", DicomFindInstance); Register("/modalities/{id}/find", DicomFind); Register("/modalities/{id}/store", DicomStore); + Register("/modalities/{id}/move", DicomMove); // For Query/Retrieve Register("/modalities/{id}/query", DicomQuery); @@ -877,5 +1059,7 @@ Register("/peers/{id}", UpdatePeer); Register("/peers/{id}", DeletePeer); Register("/peers/{id}/store", PeerStore); + + Register("/modalities/{id}/find-worklist", DicomFindWorklist); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +34,80 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../../Core/Compression/GzipCompressor.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" +#include "../../Core/HttpServer/HttpContentNegociation.h" #include "../../Core/Logging.h" +#include "../OrthancInitialization.h" +#include "../Search/LookupResource.h" +#include "../ServerContext.h" #include "../ServerToolbox.h" -#include "../FromDcmtkBridge.h" -#include "../ServerContext.h" #include "../SliceOrdering.h" namespace Orthanc { + static void AnswerDicomAsJson(RestApiCall& call, + const Json::Value& dicom, + DicomToJsonFormat mode) + { + if (mode != DicomToJsonFormat_Full) + { + Json::Value simplified; + ServerToolbox::SimplifyTags(simplified, dicom, mode); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(dicom); + } + } + + + static DicomToJsonFormat GetDicomFormat(const RestApiGetCall& call) + { + if (call.HasArgument("simplify")) + { + return DicomToJsonFormat_Human; + } + else if (call.HasArgument("short")) + { + return DicomToJsonFormat_Short; + } + else + { + return DicomToJsonFormat_Full; + } + } + + + static void AnswerDicomAsJson(RestApiGetCall& call, + const Json::Value& dicom) + { + AnswerDicomAsJson(call, dicom, GetDicomFormat(call)); + } + + + static void ParseSetOfTags(std::set<DicomTag>& target, + const RestApiGetCall& call, + const std::string& argument) + { + target.clear(); + + if (call.HasArgument(argument)) + { + std::vector<std::string> tags; + Toolbox::TokenizeString(tags, call.GetArgument(argument, ""), ','); + + for (size_t i = 0; i < tags.size(); i++) + { + target.insert(FromDcmtkBridge::ParseTag(tags[i])); + } + } + } + + // List all the patients, studies, series or instances ---------------------- static void AnswerListOfResources(RestApiOutput& output, @@ -184,78 +250,217 @@ std::string publicId = call.GetUriComponent("id", ""); std::string dicom; - context.ReadFile(dicom, publicId, FileContentType_Dicom); + context.ReadDicom(dicom, publicId); std::string target; call.BodyToString(target); - Toolbox::WriteFile(dicom, target); + SystemToolbox::WriteFile(dicom, target); call.GetOutput().AnswerBuffer("{}", "application/json"); } - template <bool simplify> + template <DicomToJsonFormat format> static void GetInstanceTags(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); + + std::set<DicomTag> ignoreTagLength; + ParseSetOfTags(ignoreTagLength, call, "ignore-length"); - if (simplify) + if (format != DicomToJsonFormat_Full || + !ignoreTagLength.empty()) { Json::Value full; - context.ReadJson(full, publicId); - - Json::Value simplified; - Toolbox::SimplifyTags(simplified, full); - call.GetOutput().AnswerJson(simplified); + context.ReadDicomAsJson(full, publicId, ignoreTagLength); + AnswerDicomAsJson(call, full, format); } else { - context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_DicomAsJson); + // This path allows to avoid the JSON decoding if no + // simplification is asked, and if no "ignore-length" argument + // is present + std::string full; + context.ReadDicomAsJson(full, publicId); + call.GetOutput().AnswerBuffer(full, "application/json"); } } static void GetInstanceTagsBis(RestApiGetCall& call) { - bool simplify = call.HasArgument("simplify"); - - if (simplify) + switch (GetDicomFormat(call)) { - GetInstanceTags<true>(call); - } - else - { - GetInstanceTags<false>(call); + case DicomToJsonFormat_Human: + GetInstanceTags<DicomToJsonFormat_Human>(call); + break; + + case DicomToJsonFormat_Short: + GetInstanceTags<DicomToJsonFormat_Short>(call); + break; + + case DicomToJsonFormat_Full: + GetInstanceTags<DicomToJsonFormat_Full>(call); + break; + + default: + throw OrthancException(ErrorCode_InternalError); } } static void ListFrames(RestApiGetCall& call) { - Json::Value instance; - if (OrthancRestApi::GetIndex(call).LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) + std::string publicId = call.GetUriComponent("id", ""); + + unsigned int numberOfFrames; + { - unsigned int numberOfFrames = 1; + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); + numberOfFrames = locker.GetDicom().GetFramesCount(); + } + + Json::Value result = Json::arrayValue; + for (unsigned int i = 0; i < numberOfFrames; i++) + { + result.append(i); + } + + call.GetOutput().AnswerJson(result); + } + - try - { - Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; - numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); - } - catch (...) + namespace + { + class ImageToEncode + { + private: + std::auto_ptr<ImageAccessor>& image_; + ImageExtractionMode mode_; + bool invert_; + std::string format_; + std::string answer_; + + public: + ImageToEncode(std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert) : + image_(image), + mode_(mode), + invert_(invert) { } - Json::Value result = Json::arrayValue; - for (unsigned int i = 0; i < numberOfFrames; i++) + void Answer(RestApiOutput& output) + { + output.AnswerBuffer(answer_, format_); + } + + void EncodeUsingPng() + { + format_ = "image/png"; + DicomImageDecoder::ExtractPngImage(answer_, image_, mode_, invert_); + } + + void EncodeUsingPam() { - result.append(i); + /** + * "No Internet Media Type (aka MIME type, content type) for + * PBM has been registered with IANA, but the unofficial value + * image/x-portable-arbitrarymap is assigned by this + * specification, to be consistent with conventional values + * for the older Netpbm formats." + * http://netpbm.sourceforge.net/doc/pam.html + **/ + format_ = "image/x-portable-arbitrarymap"; + DicomImageDecoder::ExtractPamImage(answer_, image_, mode_, invert_); + } + + void EncodeUsingJpeg(uint8_t quality) + { + format_ = "image/jpeg"; + DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, invert_, quality); + } + }; + + class EncodePng : public HttpContentNegociation::IHandler + { + private: + ImageToEncode& image_; + + public: + EncodePng(ImageToEncode& image) : image_(image) + { } - call.GetOutput().AnswerJson(result); - } + virtual void Handle(const std::string& type, + const std::string& subtype) + { + assert(type == "image"); + assert(subtype == "png"); + image_.EncodeUsingPng(); + } + }; + + class EncodePam : public HttpContentNegociation::IHandler + { + private: + ImageToEncode& image_; + + public: + EncodePam(ImageToEncode& image) : image_(image) + { + } + + virtual void Handle(const std::string& type, + const std::string& subtype) + { + assert(type == "image"); + assert(subtype == "x-portable-arbitrarymap"); + image_.EncodeUsingPam(); + } + }; + + class EncodeJpeg : public HttpContentNegociation::IHandler + { + private: + ImageToEncode& image_; + unsigned int quality_; + + public: + EncodeJpeg(ImageToEncode& image, + const RestApiGetCall& call) : + image_(image) + { + std::string v = call.GetArgument("quality", "90" /* default JPEG quality */); + bool ok = false; + + try + { + quality_ = boost::lexical_cast<unsigned int>(v); + ok = (quality_ >= 1 && quality_ <= 100); + } + catch (boost::bad_lexical_cast&) + { + } + + if (!ok) + { + LOG(ERROR) << "Bad quality for a JPEG encoding (must be a number between 0 and 100): " << v; + throw OrthancException(ErrorCode_BadRequest); + } + } + + virtual void Handle(const std::string& type, + const std::string& subtype) + { + assert(type == "image"); + assert(subtype == "jpeg"); + image_.EncodeUsingJpeg(quality_); + } + }; } @@ -271,21 +476,64 @@ { frame = boost::lexical_cast<unsigned int>(frameId); } - catch (boost::bad_lexical_cast) + catch (boost::bad_lexical_cast&) { return; } - std::string publicId = call.GetUriComponent("id", ""); - std::string dicomContent, png; - context.ReadFile(dicomContent, publicId, FileContentType_Dicom); - - ParsedDicomFile dicom(dicomContent); + bool invert = false; + std::auto_ptr<ImageAccessor> decoded; try { - dicom.ExtractPngImage(png, frame, mode); - call.GetOutput().AnswerBuffer(png, "image/png"); + std::string publicId = call.GetUriComponent("id", ""); + +#if ORTHANC_ENABLE_PLUGINS == 1 + 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 (mode == ImageExtractionMode_Preview && + 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); + + PhotometricInterpretation photometric; + if (parsed.LookupPhotometricInterpretation(photometric)) + { + invert = (photometric == PhotometricInterpretation_Monochrome1); + } + } + } +#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) { @@ -305,6 +553,23 @@ call.GetOutput().Redirect(root + "app/images/unsupported.png"); } } + + ImageToEncode image(decoded, mode, invert); + + HttpContentNegociation negociation; + EncodePng png(image); + negociation.Register("image/png", png); + + EncodeJpeg jpeg(image, call); + negociation.Register("image/jpeg", jpeg); + + EncodePam pam(image); + negociation.Register("image/x-portable-arbitrarymap", pam); + + if (negociation.Apply(call.GetHttpHeaders())) + { + image.Answer(call.GetOutput()); + } } @@ -319,28 +584,67 @@ { frame = boost::lexical_cast<unsigned int>(frameId); } - catch (boost::bad_lexical_cast) + catch (boost::bad_lexical_cast&) { return; } std::string publicId = call.GetUriComponent("id", ""); std::string dicomContent; - context.ReadFile(dicomContent, publicId, FileContentType_Dicom); + context.ReadDicom(dicomContent, publicId); - ParsedDicomFile dicom(dicomContent); - ImageBuffer buffer; - dicom.ExtractImage(buffer, frame); +#if ORTHANC_ENABLE_PLUGINS == 1 + IDicomImageDecoder& decoder = context.GetPlugins(); +#else + DefaultDicomImageDecoder decoder; // This is Orthanc's built-in decoder +#endif - ImageAccessor accessor(buffer.GetConstAccessor()); + std::auto_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame)); std::string result; - accessor.ToMatlabString(result); + decoded->ToMatlabString(result); call.GetOutput().AnswerBuffer(result, "text/plain"); } + template <bool GzipCompression> + static void GetRawFrame(RestApiGetCall& call) + { + std::string frameId = call.GetUriComponent("frame", "0"); + + unsigned int frame; + try + { + frame = boost::lexical_cast<unsigned int>(frameId); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + std::string publicId = call.GetUriComponent("id", ""); + std::string raw, mime; + + { + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); + locker.GetDicom().GetRawFrame(raw, mime, frame); + } + + if (GzipCompression) + { + GzipCompressor gzip; + std::string compressed; + gzip.Compress(compressed, raw.empty() ? NULL : raw.c_str(), raw.size()); + call.GetOutput().AnswerBuffer(compressed, "application/gzip"); + } + else + { + call.GetOutput().AnswerBuffer(raw, mime); + } + } + + static void GetResourceStatistics(RestApiGetCall& call) { @@ -369,12 +673,33 @@ std::list<MetadataType> metadata; OrthancRestApi::GetIndex(call).ListAvailableMetadata(metadata, publicId); - Json::Value result = Json::arrayValue; + + Json::Value result; - for (std::list<MetadataType>::const_iterator - it = metadata.begin(); it != metadata.end(); ++it) + if (call.HasArgument("expand")) { - result.append(EnumerationToString(*it)); + result = Json::objectValue; + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + std::string value; + if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, *it)) + { + std::string key = EnumerationToString(*it); + result[key] = value; + } + } + } + else + { + result = Json::arrayValue; + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + result.append(EnumerationToString(*it)); + } } call.GetOutput().AnswerJson(result); @@ -405,13 +730,15 @@ std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); - if (metadata >= MetadataType_StartUser && - metadata <= MetadataType_EndUser) - { - // It is forbidden to modify internal metadata + if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata + { OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); call.GetOutput().AnswerBuffer("", "text/plain"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } @@ -426,13 +753,16 @@ std::string value; call.BodyToString(value); - if (metadata >= MetadataType_StartUser && - metadata <= MetadataType_EndUser) + if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata { // It is forbidden to modify internal metadata OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); call.GetOutput().AnswerBuffer("", "text/plain"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } @@ -527,7 +857,7 @@ { // Return the raw data (possibly compressed), as stored on the filesystem std::string content; - context.ReadFile(content, publicId, type, false); + context.ReadAttachment(content, publicId, type, false); call.GetOutput().AnswerBuffer(content, "application/octet-stream"); } } @@ -596,7 +926,7 @@ // First check whether the compressed data is correctly stored in the disk std::string data; - context.ReadFile(data, publicId, StringToContentType(name), false); + context.ReadAttachment(data, publicId, StringToContentType(name), false); std::string actualMD5; Toolbox::ComputeMD5(actualMD5, data); @@ -611,7 +941,7 @@ } else { - context.ReadFile(data, publicId, StringToContentType(name), true); + context.ReadAttachment(data, publicId, StringToContentType(name), true); Toolbox::ComputeMD5(actualMD5, data); ok = (actualMD5 == info.GetUncompressedMD5()); } @@ -638,12 +968,15 @@ std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); - if (contentType >= FileContentType_StartUser && // It is forbidden to modify internal attachments - contentType <= FileContentType_EndUser && + if (IsUserContentType(contentType) && // It is forbidden to modify internal attachments context.AddAttachment(publicId, StringToContentType(name), call.GetBodyData(), call.GetBodySize())) { call.GetOutput().AnswerBuffer("{}", "application/json"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } @@ -655,13 +988,33 @@ std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); - if (contentType >= FileContentType_StartUser && - contentType <= FileContentType_EndUser) + bool allowed; + if (IsUserContentType(contentType)) + { + allowed = true; + } + else if (Configuration::GetGlobalBoolParameter("StoreDicom", true) && + contentType == FileContentType_DicomAsJson) { - // It is forbidden to delete internal attachments + allowed = true; + } + else + { + // It is forbidden to delete internal attachments, except for + // the "DICOM as JSON" summary as of Orthanc 1.2.0 (this summary + // would be automatically reconstructed on the next GET call) + allowed = false; + } + + if (allowed) + { OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); call.GetOutput().AnswerBuffer("{}", "application/json"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } @@ -724,7 +1077,7 @@ try { - context.ReadJson(tags, *it); + context.ReadDicomAsJson(tags, *it); } catch (OrthancException&) { @@ -782,22 +1135,12 @@ { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); - bool simplify = call.HasArgument("simplify"); Json::Value sharedTags; if (ExtractSharedTags(sharedTags, context, publicId)) { // Success: Send the value of the shared tags - if (simplify) - { - Json::Value simplified; - Toolbox::SimplifyTags(simplified, sharedTags); - call.GetOutput().AnswerJson(simplified); - } - else - { - call.GetOutput().AnswerJson(sharedTags); - } + AnswerDicomAsJson(call, sharedTags); } } @@ -818,7 +1161,9 @@ ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); - bool simplify = call.HasArgument("simplify"); + + std::set<DicomTag> ignoreTagLength; + ParseSetOfTags(ignoreTagLength, call, "ignore-length"); typedef std::set<DicomTag> ModuleTags; ModuleTags moduleTags; @@ -842,7 +1187,7 @@ publicId = instances.front(); } - context.ReadJson(tags, publicId); + context.ReadDicomAsJson(tags, publicId, ignoreTagLength); // Filter the tags of the instance according to the module Json::Value result = Json::objectValue; @@ -855,16 +1200,7 @@ } } - if (simplify) - { - Json::Value simplified; - Toolbox::SimplifyTags(simplified, result); - call.GetOutput().AnswerJson(simplified); - } - else - { - call.GetOutput().AnswerJson(result); - } + AnswerDicomAsJson(call, result); } @@ -943,7 +1279,8 @@ request["Level"].type() == Json::stringValue && request["Query"].type() == Json::objectValue && (!request.isMember("CaseSensitive") || request["CaseSensitive"].type() == Json::booleanValue) && - (!request.isMember("Limit") || request["Limit"].type() == Json::intValue)) + (!request.isMember("Limit") || request["Limit"].type() == Json::intValue) && + (!request.isMember("Since") || request["Since"].type() == Json::intValue)) { bool expand = false; if (request.isMember("Expand")) @@ -960,11 +1297,25 @@ size_t limit = 0; if (request.isMember("Limit")) { - limit = request["CaseSensitive"].asInt(); - if (limit < 0) + int tmp = request["Limit"].asInt(); + if (tmp < 0) { throw OrthancException(ErrorCode_ParameterOutOfRange); } + + limit = static_cast<size_t>(tmp); + } + + size_t since = 0; + if (request.isMember("Since")) + { + int tmp = request["Since"].asInt(); + if (tmp < 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + since = static_cast<size_t>(tmp); } std::string level = request["Level"].asString(); @@ -983,10 +1334,12 @@ request["Query"][members[i]].asString(), caseSensitive); } - + + bool isComplete; std::list<std::string> resources; - context.Apply(resources, query, limit); - AnswerListOfResources(call.GetOutput(), context.GetIndex(), resources, query.GetLevel(), expand); + context.Apply(isComplete, resources, query, since, limit); + AnswerListOfResources(call.GetOutput(), context.GetIndex(), + resources, query.GetLevel(), expand); } else { @@ -1043,7 +1396,10 @@ { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); - bool simplify = call.HasArgument("simplify"); + DicomToJsonFormat format = GetDicomFormat(call); + + std::set<DicomTag> ignoreTagLength; + ParseSetOfTags(ignoreTagLength, call, "ignore-length"); // Retrieve all the instances of this patient/study/series typedef std::list<std::string> Instances; @@ -1057,12 +1413,12 @@ it != instances.end(); ++it) { Json::Value full; - context.ReadJson(full, *it); + context.ReadDicomAsJson(full, *it, ignoreTagLength); - if (simplify) + if (format != DicomToJsonFormat_Full) { Json::Value simplified; - Toolbox::SimplifyTags(simplified, full); + ServerToolbox::SimplifyTags(simplified, full, format); result[*it] = simplified; } else @@ -1138,6 +1494,79 @@ } + static void GetInstanceHeader(RestApiGetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + + std::string dicomContent; + context.ReadDicom(dicomContent, publicId); + + // TODO Consider using "DicomMap::ParseDicomMetaInformation()" to + // speed up things here + + ParsedDicomFile dicom(dicomContent); + + Json::Value header; + dicom.HeaderToJson(header, DicomToJsonFormat_Full); + + AnswerDicomAsJson(call, header); + } + + + static void InvalidateTags(RestApiPostCall& call) + { + ServerIndex& index = OrthancRestApi::GetIndex(call); + + // Loop over the instances, grouping them by parent studies so as + // to avoid large memory consumption + std::list<std::string> studies; + index.GetAllUuids(studies, ResourceType_Study); + + for (std::list<std::string>::const_iterator + study = studies.begin(); study != studies.end(); ++study) + { + std::list<std::string> instances; + index.GetChildInstances(instances, *study); + + for (std::list<std::string>::const_iterator + instance = instances.begin(); instance != instances.end(); ++instance) + { + index.DeleteAttachment(*instance, FileContentType_DicomAsJson); + } + } + + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + template <enum ResourceType type> + static void ReconstructResource(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + ServerToolbox::ReconstructResource(context, call.GetUriComponent("id", "")); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + static void ReconstructAllResources(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::list<std::string> studies; + context.GetIndex().GetAllUuids(studies, ResourceType_Study); + + for (std::list<std::string>::const_iterator + study = studies.begin(); study != studies.end(); ++study) + { + ServerToolbox::ReconstructResource(context, *study); + } + + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + void OrthancRestApi::RegisterResources() { Register("/instances", ListResources<ResourceType_Instance>); @@ -1172,7 +1601,7 @@ Register("/instances/{id}/file", GetInstanceFile); Register("/instances/{id}/export", ExportInstanceFile); Register("/instances/{id}/tags", GetInstanceTagsBis); - Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); + Register("/instances/{id}/simplified-tags", GetInstanceTags<DicomToJsonFormat_Human>); Register("/instances/{id}/frames", ListFrames); Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); @@ -1180,12 +1609,15 @@ Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); + Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); + Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); Register("/instances/{id}/pdf", ExtractPdf); Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/matlab", GetMatlabImage); + Register("/instances/{id}/header", GetInstanceHeader); Register("/patients/{id}/protected", IsProtectedPatient); Register("/patients/{id}/protected", SetPatientProtection); @@ -1210,6 +1642,7 @@ Register("/{resourceType}/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression<CompressionType_None>); Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); + Register("/tools/invalidate-tags", InvalidateTags); Register("/tools/lookup", Lookup); Register("/tools/find", Find); @@ -1234,5 +1667,11 @@ Register("/instances/{id}/content/*", GetRawContent); Register("/series/{id}/ordered-slices", OrderSlices); + + Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); + Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>); + Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>); + Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>); + Register("/tools/reconstruct", ReconstructAllResources); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +35,7 @@ #include "OrthancRestApi.h" #include "../OrthancInitialization.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Plugins/Engine/PluginsManager.h" #include "../../Plugins/Engine/OrthancPlugins.h" #include "../ServerContext.h" @@ -53,17 +54,18 @@ { Json::Value result = Json::objectValue; + result["ApiVersion"] = ORTHANC_API_VERSION; result["DatabaseVersion"] = OrthancRestApi::GetIndex(call).GetDatabaseVersion(); result["DicomAet"] = Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"); - result["DicomPort"] = Configuration::GetGlobalIntegerParameter("DicomPort", 4242); - result["HttpPort"] = Configuration::GetGlobalIntegerParameter("HttpPort", 8042); + result["DicomPort"] = Configuration::GetGlobalUnsignedIntegerParameter("DicomPort", 4242); + result["HttpPort"] = Configuration::GetGlobalUnsignedIntegerParameter("HttpPort", 8042); result["Name"] = Configuration::GetGlobalStringParameter("Name", ""); result["Version"] = ORTHANC_VERSION; result["StorageAreaPlugin"] = Json::nullValue; result["DatabaseBackendPlugin"] = Json::nullValue; -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 result["PluginsEnabled"] = true; const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins(); @@ -122,16 +124,17 @@ call.BodyToString(command); { - LuaScripting::Locker locker(context.GetLua()); - locker.GetLua().Execute(result, command); + LuaScripting::Lock lock(context.GetLuaScripting()); + lock.GetLua().Execute(result, command); } call.GetOutput().AnswerBuffer(result, "text/plain"); } + template <bool UTC> static void GetNowIsoString(RestApiGetCall& call) { - call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain"); + call.GetOutput().AnswerBuffer(SystemToolbox::GetNowIsoString(UTC), "text/plain"); } @@ -143,6 +146,24 @@ } + static void GetDefaultEncoding(RestApiGetCall& call) + { + Encoding encoding = GetDefaultDicomEncoding(); + call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); + } + + + static void SetDefaultEncoding(RestApiPutCall& call) + { + Encoding encoding = StringToEncoding(call.GetBodyData()); + + Configuration::SetDefaultEncoding(encoding); + + call.GetOutput().AnswerBuffer(EnumerationToString(encoding), "text/plain"); + } + + + // Plugins information ------------------------------------------------------ static void ListPlugins(RestApiGetCall& call) @@ -153,7 +174,7 @@ if (OrthancRestApi::GetContext(call).HasPlugins()) { -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 std::list<std::string> plugins; OrthancRestApi::GetContext(call).GetPlugins().GetManager().ListPlugins(plugins); @@ -176,7 +197,7 @@ return; } -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPlugins().GetManager(); std::string id = call.GetUriComponent("id", ""); @@ -224,7 +245,7 @@ if (OrthancRestApi::GetContext(call).HasPlugins()) { -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins(); const PluginsManager& manager = plugins.GetManager(); @@ -248,6 +269,99 @@ } + + + // Jobs information ------------------------------------------------------ + + static void ListJobs(RestApiGetCall& call) + { + bool expand = call.HasArgument("expand"); + + Json::Value v = Json::arrayValue; + + std::set<std::string> jobs; + OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().ListJobs(jobs); + + for (std::set<std::string>::const_iterator it = jobs.begin(); + it != jobs.end(); ++it) + { + if (expand) + { + JobInfo info; + if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, *it)) + { + Json::Value tmp; + info.Format(tmp); + v.append(tmp); + } + } + else + { + v.append(*it); + } + } + + call.GetOutput().AnswerJson(v); + } + + static void GetJobInfo(RestApiGetCall& call) + { + std::string id = call.GetUriComponent("id", ""); + + JobInfo info; + if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, id)) + { + Json::Value json; + info.Format(json); + call.GetOutput().AnswerJson(json); + } + } + + + enum JobAction + { + JobAction_Cancel, + JobAction_Pause, + JobAction_Resubmit, + JobAction_Resume + }; + + template <JobAction action> + static void ApplyJobAction(RestApiPostCall& call) + { + std::string id = call.GetUriComponent("id", ""); + + bool ok = false; + + switch (action) + { + case JobAction_Cancel: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Cancel(id); + break; + + case JobAction_Pause: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Pause(id); + break; + + case JobAction_Resubmit: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resubmit(id); + break; + + case JobAction_Resume: + ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resume(id); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (ok) + { + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + } + + void OrthancRestApi::RegisterSystem() { Register("/", ServeRoot); @@ -255,11 +369,21 @@ Register("/statistics", GetStatistics); Register("/tools/generate-uid", GenerateUid); Register("/tools/execute-script", ExecuteScript); - Register("/tools/now", GetNowIsoString); + Register("/tools/now", GetNowIsoString<true>); + Register("/tools/now-local", GetNowIsoString<false>); Register("/tools/dicom-conformance", GetDicomConformanceStatement); + Register("/tools/default-encoding", GetDefaultEncoding); + Register("/tools/default-encoding", SetDefaultEncoding); Register("/plugins", ListPlugins); Register("/plugins/{id}", GetPlugin); Register("/plugins/explorer.js", GetOrthancExplorerPlugins); + + Register("/jobs", ListJobs); + Register("/jobs/{id}", GetJobInfo); + Register("/jobs/{id}/cancel", ApplyJobAction<JobAction_Cancel>); + Register("/jobs/{id}/pause", ApplyJobAction<JobAction_Pause>); + Register("/jobs/{id}/resubmit", ApplyJobAction<JobAction_Resubmit>); + Register("/jobs/{id}/resume", ApplyJobAction<JobAction_Resume>); } }
--- a/OrthancServer/ParsedDicomFile.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1225 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: GDCM (Grassroots DICOM). A DICOM library - Module: http://gdcm.sourceforge.net/Copyright.html - -Copyright (c) 2006-2011 Mathieu Malaterre -Copyright (c) 1993-2005 CREATIS -(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any - contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to - endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "PrecompiledHeadersServer.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "ParsedDicomFile.h" - -#include "ServerToolbox.h" -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" -#include "Internals/DicomImageDecoder.h" -#include "../Core/Logging.h" -#include "../Core/Toolbox.h" -#include "../Core/OrthancException.h" -#include "../Core/Images/ImageBuffer.h" -#include "../Core/Images/PngWriter.h" -#include "../Core/Uuid.h" -#include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" -#include "../Core/Images/PngReader.h" - -#include <list> -#include <limits> - -#include <boost/lexical_cast.hpp> - -#include <dcmtk/dcmdata/dcchrstr.h> -#include <dcmtk/dcmdata/dcdicent.h> -#include <dcmtk/dcmdata/dcdict.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcdeftag.h> - -#include <dcmtk/dcmdata/dcvrae.h> -#include <dcmtk/dcmdata/dcvras.h> -#include <dcmtk/dcmdata/dcvrcs.h> -#include <dcmtk/dcmdata/dcvrda.h> -#include <dcmtk/dcmdata/dcvrds.h> -#include <dcmtk/dcmdata/dcvrdt.h> -#include <dcmtk/dcmdata/dcvrfd.h> -#include <dcmtk/dcmdata/dcvrfl.h> -#include <dcmtk/dcmdata/dcvris.h> -#include <dcmtk/dcmdata/dcvrlo.h> -#include <dcmtk/dcmdata/dcvrlt.h> -#include <dcmtk/dcmdata/dcvrpn.h> -#include <dcmtk/dcmdata/dcvrsh.h> -#include <dcmtk/dcmdata/dcvrsl.h> -#include <dcmtk/dcmdata/dcvrss.h> -#include <dcmtk/dcmdata/dcvrst.h> -#include <dcmtk/dcmdata/dcvrtm.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcvrul.h> -#include <dcmtk/dcmdata/dcvrus.h> -#include <dcmtk/dcmdata/dcvrut.h> -#include <dcmtk/dcmdata/dcpixel.h> -#include <dcmtk/dcmdata/dcpixseq.h> -#include <dcmtk/dcmdata/dcpxitem.h> - - -#include <boost/math/special_functions/round.hpp> -#include <dcmtk/dcmdata/dcostrmb.h> -#include <boost/algorithm/string/predicate.hpp> - - -static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; - - - -namespace Orthanc -{ - struct ParsedDicomFile::PImpl - { - std::auto_ptr<DcmFileFormat> file_; - }; - - - // This method can only be called from the constructors! - void ParsedDicomFile::Setup(const char* buffer, size_t size) - { - DcmInputBufferStream is; - if (size > 0) - { - is.setBuffer(buffer, size); - } - is.setEos(); - - pimpl_->file_.reset(new DcmFileFormat); - pimpl_->file_->transferInit(); - if (!pimpl_->file_->read(is).good()) - { - delete pimpl_; // Avoid a memory leak due to exception - // throwing, as we are in the constructor - - throw OrthancException(ErrorCode_BadFileFormat); - } - pimpl_->file_->loadAllDataIntoMemory(); - pimpl_->file_->transferEnd(); - } - - - static void SendPathValueForDictionary(RestApiOutput& output, - DcmItem& dicom) - { - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < dicom.card(); i++) - { - DcmElement* element = dicom.getElement(i); - if (element) - { - char buf[16]; - sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); - v.append(buf); - } - } - - output.AnswerJson(v); - } - - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - static void ParseTagAndGroup(DcmTagKey& key, - const std::string& tag) - { - DicomTag t = FromDcmtkBridge::ParseTag(tag); - key = DcmTagKey(t.GetGroup(), t.GetElement()); - } - - - static void SendSequence(RestApiOutput& output, - DcmSequenceOfItems& sequence) - { - // This element is a sequence - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < sequence.card(); i++) - { - v.append(boost::lexical_cast<std::string>(i)); - } - - output.AnswerJson(v); - } - - - static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, - E_TransferSyntax transferSyntax) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - return pixelSequence->card(); - } - else - { - return 1; - } - } - - - namespace - { - class DicomFieldStream : public IHttpStreamAnswer - { - private: - DcmElement& element_; - uint32_t length_; - uint32_t offset_; - std::string chunk_; - size_t chunkSize_; - - public: - DicomFieldStream(DcmElement& element, - E_TransferSyntax transferSyntax) : - element_(element), - length_(element.getLength(transferSyntax)), - offset_(0), - chunkSize_(0) - { - static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB - chunk_.resize(CHUNK_SIZE); - } - - virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, - bool /*deflateAllowed*/) - { - // No support for compression - return HttpCompression_None; - } - - virtual bool HasContentFilename(std::string& filename) - { - return false; - } - - virtual std::string GetContentType() - { - return ""; - } - - virtual uint64_t GetContentLength() - { - return length_; - } - - virtual bool ReadNextChunk() - { - assert(offset_ <= length_); - - if (offset_ == length_) - { - return false; - } - else - { - if (length_ - offset_ < chunk_.size()) - { - chunkSize_ = length_ - offset_; - } - else - { - chunkSize_ = chunk_.size(); - } - - OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_); - - offset_ += chunkSize_; - - if (!cond.good()) - { - LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); - throw OrthancException(ErrorCode_InternalError); - } - - return true; - } - } - - virtual const char *GetChunkContent() - { - return chunk_.c_str(); - } - - virtual size_t GetChunkSize() - { - return chunkSize_; - } - }; - } - - - static bool AnswerPixelData(RestApiOutput& output, - DcmItem& dicom, - E_TransferSyntax transferSyntax, - const std::string* blockUri) - { - DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), - DICOM_TAG_PIXEL_DATA.GetElement()); - - DcmElement *element = NULL; - if (!dicom.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - try - { - DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); - if (blockUri == NULL) - { - // The user asks how many blocks are presents in this pixel data - unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); - - Json::Value result(Json::arrayValue); - for (unsigned int i = 0; i < blocks; i++) - { - result.append(boost::lexical_cast<std::string>(i)); - } - - output.AnswerJson(result); - return true; - } - - - unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); - - if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - // This is the case for JPEG transfer syntaxes - if (block < pixelSequence->card()) - { - DcmPixelItem* pixelItem = NULL; - if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) - { - if (pixelItem->getLength() == 0) - { - output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); - return true; - } - - Uint8* buffer = NULL; - if (pixelItem->getUint8Array(buffer).good() && buffer) - { - output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); - return true; - } - } - } - } - else - { - // This is the case for raw, uncompressed image buffers - assert(*blockUri == "0"); - DicomFieldStream stream(*element, transferSyntax); - output.AnswerStream(stream); - } - } - } - catch (boost::bad_lexical_cast&) - { - // The URI entered by the user is not a number - } - catch (std::bad_cast&) - { - // This should never happen - } - - return false; - } - - - - static void SendPathValueForLeaf(RestApiOutput& output, - const std::string& tag, - DcmItem& dicom, - E_TransferSyntax transferSyntax) - { - DcmTagKey k; - ParseTagAndGroup(k, tag); - - DcmSequenceOfItems* sequence = NULL; - if (dicom.findAndGetSequence(k, sequence).good() && - sequence != NULL && - sequence->getVR() == EVR_SQ) - { - SendSequence(output, *sequence); - return; - } - - DcmElement* element = NULL; - if (dicom.findAndGetElement(k, element).good() && - element != NULL && - //element->getVR() != EVR_UNKNOWN && // This would forbid private tags - element->getVR() != EVR_SQ) - { - DicomFieldStream stream(*element, transferSyntax); - output.AnswerStream(stream); - } - } - - void ParsedDicomFile::SendPathValue(RestApiOutput& output, - const UriComponents& uri) - { - DcmItem* dicom = pimpl_->file_->getDataset(); - E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); - - // Special case: Accessing the pixel data - if (uri.size() == 1 || - uri.size() == 2) - { - DcmTagKey tag; - ParseTagAndGroup(tag, uri[0]); - - if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && - tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) - { - AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); - return; - } - } - - // Go down in the tag hierarchy according to the URI - for (size_t pos = 0; pos < uri.size() / 2; pos++) - { - size_t index; - try - { - index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); - } - catch (boost::bad_lexical_cast&) - { - return; - } - - DcmTagKey k; - DcmItem *child = NULL; - ParseTagAndGroup(k, uri[2 * pos]); - if (!dicom->findAndGetSequenceItem(k, child, index).good() || - child == NULL) - { - return; - } - - dicom = child; - } - - // We have reached the end of the URI - if (uri.size() % 2 == 0) - { - SendPathValueForDictionary(output, *dicom); - } - else - { - SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); - } - } - - - void ParsedDicomFile::Remove(const DicomTag& tag) - { - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = pimpl_->file_->getDataset()->remove(key); - if (element != NULL) - { - delete element; - } - } - - - - void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep) - { - DcmDataset& dataset = *pimpl_->file_->getDataset(); - - // Loop over the dataset to detect its private tags - typedef std::list<DcmElement*> Tags; - Tags privateTags; - - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - DcmTag tag(element->getTag()); - - // Is this a private tag? - if (tag.isPrivate()) - { - bool remove = true; - - // Check whether this private tag is to be kept - if (toKeep != NULL) - { - DicomTag tmp = FromDcmtkBridge::Convert(tag); - if (toKeep->find(tmp) != toKeep->end()) - { - remove = false; // Keep it - } - } - - if (remove) - { - privateTags.push_back(element); - } - } - } - - // Loop over the detected private tags to remove them - for (Tags::iterator it = privateTags.begin(); - it != privateTags.end(); ++it) - { - DcmElement* tmp = dataset.remove(*it); - if (tmp != NULL) - { - delete tmp; - } - } - } - - - static void InsertInternal(DcmDataset& dicom, - DcmElement* element) - { - OFCondition cond = dicom.insert(element, false, false); - if (!cond.good()) - { - // This field already exists - delete element; - throw OrthancException(ErrorCode_InternalError); - } - } - - - void ParsedDicomFile::Insert(const DicomTag& tag, - const Json::Value& value, - bool decodeBinaryTags) - { - std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding())); - InsertInternal(*pimpl_->file_->getDataset(), element.release()); - } - - - static void ReplaceInternal(DcmDataset& dicom, - std::auto_ptr<DcmElement>& element, - DicomReplaceMode mode) - { - const DcmTagKey& tag = element->getTag(); - - if (!dicom.findAndDeleteElement(tag).good()) - { - // This field does not exist, act wrt. the specified "mode" - switch (mode) - { - case DicomReplaceMode_InsertIfAbsent: - break; - - case DicomReplaceMode_ThrowIfAbsent: - throw OrthancException(ErrorCode_InexistentItem); - - case DicomReplaceMode_IgnoreIfAbsent: - return; - } - } - - // Either the tag was not existing, or the replace mode was set to - // "InsertIfAbsent" - InsertInternal(dicom, element.release()); - } - - - void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, - const std::string& utf8Value, - bool decodeBinaryTags) - { - if (tag != DICOM_TAG_SOP_CLASS_UID && - tag != DICOM_TAG_SOP_INSTANCE_UID) - { - return; - } - - std::string binary; - const std::string* decoded = &utf8Value; - - if (decodeBinaryTags && - boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) - { - std::string mime; - Toolbox::DecodeDataUriScheme(mime, binary, utf8Value); - decoded = &binary; - } - else - { - Encoding encoding = GetEncoding(); - if (GetEncoding() != Encoding_Utf8) - { - binary = Toolbox::ConvertFromUtf8(utf8Value, encoding); - decoded = &binary; - } - } - - /** - * dcmodify will automatically correct 'Media Storage SOP Class - * UID' and 'Media Storage SOP Instance UID' in the metaheader, if - * you make changes to the related tags in the dataset ('SOP Class - * UID' and 'SOP Instance UID') via insert or modify mode - * options. You can disable this behaviour by using the -nmu - * option. - **/ - - if (tag == DICOM_TAG_SOP_CLASS_UID) - { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded, DicomReplaceMode_InsertIfAbsent); - } - - if (tag == DICOM_TAG_SOP_INSTANCE_UID) - { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded, DicomReplaceMode_InsertIfAbsent); - } - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const std::string& utf8Value, - DicomReplaceMode mode) - { - std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); - FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, false, GetEncoding()); - ReplaceInternal(*pimpl_->file_->getDataset(), element, mode); - UpdateStorageUid(tag, utf8Value, false); - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const Json::Value& value, - bool decodeBinaryTags, - DicomReplaceMode mode) - { - std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding())); - ReplaceInternal(*pimpl_->file_->getDataset(), element, mode); - - if (tag == DICOM_TAG_SOP_CLASS_UID || - tag == DICOM_TAG_SOP_INSTANCE_UID) - { - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - UpdateStorageUid(tag, value.asString(), decodeBinaryTags); - } - } - - - void ParsedDicomFile::Answer(RestApiOutput& output) - { - std::string serialized; - if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset())) - { - output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); - } - } - - - - bool ParsedDicomFile::GetTagValue(std::string& value, - const DicomTag& tag) - { - DcmTagKey k(tag.GetGroup(), tag.GetElement()); - DcmDataset& dataset = *pimpl_->file_->getDataset(); - - if (FromDcmtkBridge::IsPrivateTag(tag) || - FromDcmtkBridge::IsUnknownTag(tag) || - tag == DICOM_TAG_PIXEL_DATA || - tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) - { - const Uint8* data = NULL; // This is freed in the destructor of the dataset - long unsigned int count = 0; - - if (dataset.findAndGetUint8Array(k, data, &count).good()) - { - if (count > 0) - { - assert(data != NULL); - value.assign(reinterpret_cast<const char*>(data), count); - } - else - { - value.clear(); - } - - return true; - } - else - { - return false; - } - } - else - { - DcmElement* element = NULL; - if (!dataset.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement - (*element, DicomToJsonFlags_Default, GetEncoding())); - - if (v.get() == NULL || - v->IsNull()) - { - value = ""; - } - else - { - // TODO v->IsBinary() - value = v->GetContent(); - } - - return true; - } - } - - - DicomInstanceHasher ParsedDicomFile::GetHasher() - { - std::string patientId, studyUid, seriesUid, instanceUid; - - if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) || - !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || - !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || - !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); - } - - - template <typename T> - static void ExtractPngImageTruncate(std::string& result, - DicomIntegerPixelAccessor& accessor, - PixelFormat format) - { - assert(accessor.GetInformation().GetChannelCount() == 1); - - PngWriter w; - - std::vector<T> image(accessor.GetInformation().GetWidth() * accessor.GetInformation().GetHeight(), 0); - T* pixel = &image[0]; - for (unsigned int y = 0; y < accessor.GetInformation().GetHeight(); y++) - { - for (unsigned int x = 0; x < accessor.GetInformation().GetWidth(); x++, pixel++) - { - int32_t v = accessor.GetValue(x, y); - if (v < static_cast<int32_t>(std::numeric_limits<T>::min())) - *pixel = std::numeric_limits<T>::min(); - else if (v > static_cast<int32_t>(std::numeric_limits<T>::max())) - *pixel = std::numeric_limits<T>::max(); - else - *pixel = static_cast<T>(v); - } - } - - w.WriteToMemory(result, accessor.GetInformation().GetWidth(), accessor.GetInformation().GetHeight(), - accessor.GetInformation().GetWidth() * sizeof(T), format, &image[0]); - } - - - void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer) - { - FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset()); - } - - - void ParsedDicomFile::SaveToFile(const std::string& path) - { - // TODO Avoid using a temporary memory buffer, write directly on disk - std::string content; - SaveToMemoryBuffer(content); - Toolbox::WriteFile(content, path); - } - - - ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl) - { - pimpl_->file_.reset(new DcmFileFormat); - Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); - Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); - Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); - Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); - } - - - ParsedDicomFile::ParsedDicomFile(const char* content, size_t size) : pimpl_(new PImpl) - { - Setup(content, size); - } - - ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl) - { - if (content.size() == 0) - { - Setup(NULL, 0); - } - else - { - Setup(&content[0], content.size()); - } - } - - - ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other) : - pimpl_(new PImpl) - { - pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone())); - - // Create a new instance-level identifier - Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); - } - - - ParsedDicomFile::~ParsedDicomFile() - { - delete pimpl_; - } - - - void* ParsedDicomFile::GetDcmtkObject() - { - return pimpl_->file_.get(); - } - - - ParsedDicomFile* ParsedDicomFile::Clone() - { - return new ParsedDicomFile(*this); - } - - - void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme) - { - std::string mime, content; - Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme); - Toolbox::ToLowerCase(mime); - - if (mime == "image/png") - { - EmbedImage(mime, content); - } - else if (mime == "application/pdf") - { - EmbedPdf(content); - } - else - { - LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime; - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ParsedDicomFile::EmbedImage(const std::string& mime, - const std::string& content) - { - if (mime == "image/png") - { - PngReader reader; - reader.ReadFromMemory(content); - EmbedImage(reader); - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor) - { - if (accessor.GetFormat() != PixelFormat_Grayscale8 && - accessor.GetFormat() != PixelFormat_Grayscale16 && - accessor.GetFormat() != PixelFormat_RGB24 && - accessor.GetFormat() != PixelFormat_RGBA32) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if (accessor.GetFormat() == PixelFormat_RGBA32) - { - LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM"; - } - - // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html - - Remove(DICOM_TAG_PIXEL_DATA); - Replace(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth())); - Replace(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight())); - Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); - Replace(DICOM_TAG_NUMBER_OF_FRAMES, "1"); - Replace(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels - Replace(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved - Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); - Replace(DICOM_TAG_BITS_ALLOCATED, "8"); - Replace(DICOM_TAG_BITS_STORED, "8"); - Replace(DICOM_TAG_HIGH_BIT, "7"); - - unsigned int bytesPerPixel = 1; - - switch (accessor.GetFormat()) - { - case PixelFormat_RGB24: - case PixelFormat_RGBA32: - Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB"); - Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "3"); - bytesPerPixel = 3; - break; - - case PixelFormat_Grayscale8: - break; - - case PixelFormat_Grayscale16: - Replace(DICOM_TAG_BITS_ALLOCATED, "16"); - Replace(DICOM_TAG_BITS_STORED, "16"); - Replace(DICOM_TAG_HIGH_BIT, "15"); - bytesPerPixel = 2; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), - DICOM_TAG_PIXEL_DATA.GetElement()); - - std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key)); - - unsigned int pitch = accessor.GetWidth() * bytesPerPixel; - Uint8* target = NULL; - pixels->createUint8Array(accessor.GetHeight() * pitch, target); - - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - switch (accessor.GetFormat()) - { - case PixelFormat_RGB24: - case PixelFormat_Grayscale8: - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch); - target += pitch; - break; - } - - case PixelFormat_RGBA32: - { - // The alpha channel is not supported by the DICOM standard - const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)); - for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4) - { - target[0] = source[0]; - target[1] = source[1]; - target[2] = source[2]; - } - - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - void ParsedDicomFile::ExtractImage(ImageBuffer& result, - unsigned int frame) - { - DcmDataset& dataset = *pimpl_->file_->getDataset(); - - if (!DicomImageDecoder::Decode(result, dataset, frame)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void ParsedDicomFile::ExtractImage(ImageBuffer& result, - unsigned int frame, - ImageExtractionMode mode) - { - DcmDataset& dataset = *pimpl_->file_->getDataset(); - - bool ok = false; - - switch (mode) - { - case ImageExtractionMode_UInt8: - ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8, false); - break; - - case ImageExtractionMode_UInt16: - ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16, false); - break; - - case ImageExtractionMode_Int16: - ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16, false); - break; - - case ImageExtractionMode_Preview: - ok = DicomImageDecoder::DecodePreview(result, dataset, frame); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (!ok) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void ParsedDicomFile::ExtractPngImage(std::string& result, - unsigned int frame, - ImageExtractionMode mode) - { - ImageBuffer buffer; - ExtractImage(buffer, frame, mode); - - ImageAccessor accessor(buffer.GetConstAccessor()); - PngWriter writer; - writer.WriteToMemory(result, accessor); - } - - - Encoding ParsedDicomFile::GetEncoding() const - { - return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset()); - } - - - void ParsedDicomFile::SetEncoding(Encoding encoding) - { - if (encoding == Encoding_Windows1251) - { - // This Cyrillic codepage is not officially supported by the - // DICOM standard. Do not set the SpecificCharacterSet tag. - return; - } - - std::string s = GetDicomSpecificCharacterSet(encoding); - Replace(DICOM_TAG_SPECIFIC_CHARACTER_SET, s, DicomReplaceMode_InsertIfAbsent); - } - - void ParsedDicomFile::ToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset(), format, flags, maxStringLength); - } - - - bool ParsedDicomFile::HasTag(const DicomTag& tag) const - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - return pimpl_->file_->getDataset()->tagExists(key); - } - - - void ParsedDicomFile::EmbedPdf(const std::string& pdf) - { - if (pdf.size() < 5 || // (*) - strncmp("%PDF-", pdf.c_str(), 5) != 0) - { - LOG(ERROR) << "Not a PDF file"; - throw OrthancException(ErrorCode_BadFileFormat); - } - - Replace(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage); - Replace(FromDcmtkBridge::Convert(DCM_Modality), "OT"); - Replace(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD"); - Replace(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf"); - //Replace(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); - - std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); - - size_t s = pdf.size(); - if (s & 1) - { - // The size of the buffer must be even - s += 1; - } - - Uint8* bytes = NULL; - OFCondition result = element->createUint8Array(s, bytes); - if (!result.good() || bytes == NULL) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) ) - bytes[s - 1] = 0; - - memcpy(bytes, pdf.c_str(), pdf.size()); - - DcmPolymorphOBOW* obj = element.release(); - result = pimpl_->file_->getDataset()->insert(obj); - - if (!result.good()) - { - delete obj; - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - - bool ParsedDicomFile::ExtractPdf(std::string& pdf) - { - std::string sop, mime; - - if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) || - !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) || - sop != UID_EncapsulatedPDFStorage || - mime != "application/pdf") - { - return false; - } - - if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT)) - { - return false; - } - - // Strip the possible pad byte at the end of file, because the - // encapsulated documents must always have an even length. The PDF - // format expects files to end with %%EOF followed by CR/LF. If - // the last character of the file is not a CR or LF, we assume it - // is a pad byte and remove it. - if (pdf.size() > 0) - { - char last = *pdf.rbegin(); - - if (last != 10 && last != 13) - { - pdf.resize(pdf.size() - 1); - } - } - - return true; - } - - - void ParsedDicomFile::Convert(DicomMap& tags) - { - FromDcmtkBridge::Convert(tags, *pimpl_->file_->getDataset()); - } -}
--- a/OrthancServer/ParsedDicomFile.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Core/DicomFormat/DicomInstanceHasher.h" -#include "../Core/RestApi/RestApiOutput.h" -#include "ServerEnumerations.h" -#include "../Core/Images/ImageAccessor.h" -#include "../Core/Images/ImageBuffer.h" -#include "../Core/IDynamicObject.h" - -namespace Orthanc -{ - class ParsedDicomFile : public IDynamicObject - { - private: - struct PImpl; - PImpl* pimpl_; - - ParsedDicomFile(ParsedDicomFile& other); - - void Setup(const char* content, - size_t size); - - void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep); - - void UpdateStorageUid(const DicomTag& tag, - const std::string& value, - bool decodeBinaryTags); - - public: - ParsedDicomFile(); // Create a minimal DICOM instance - - ParsedDicomFile(const char* content, - size_t size); - - ParsedDicomFile(const std::string& content); - - ~ParsedDicomFile(); - - void* GetDcmtkObject(); - - ParsedDicomFile* Clone(); - - void SendPathValue(RestApiOutput& output, - const UriComponents& uri); - - void Answer(RestApiOutput& output); - - void Remove(const DicomTag& tag); - - void Replace(const DicomTag& tag, - const std::string& utf8Value, - DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); - - void Replace(const DicomTag& tag, - const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeBinaryTags, - DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); - - void Insert(const DicomTag& tag, - const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeBinaryTags); - - void RemovePrivateTags() - { - RemovePrivateTagsInternal(NULL); - } - - void RemovePrivateTags(const std::set<DicomTag>& toKeep) - { - RemovePrivateTagsInternal(&toKeep); - } - - bool GetTagValue(std::string& value, - const DicomTag& tag); - - DicomInstanceHasher GetHasher(); - - void SaveToMemoryBuffer(std::string& buffer); - - void SaveToFile(const std::string& path); - - void EmbedContent(const std::string& dataUriScheme); - - void EmbedImage(const ImageAccessor& accessor); - - void EmbedImage(const std::string& mime, - const std::string& content); - - void ExtractImage(ImageBuffer& result, - unsigned int frame); - - void ExtractImage(ImageBuffer& result, - unsigned int frame, - ImageExtractionMode mode); - - void ExtractPngImage(std::string& result, - unsigned int frame, - ImageExtractionMode mode); - - Encoding GetEncoding() const; - - void SetEncoding(Encoding encoding); - - void ToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); - - bool HasTag(const DicomTag& tag) const; - - void EmbedPdf(const std::string& pdf); - - bool ExtractPdf(std::string& pdf); - - void Convert(DicomMap& tags); - }; - -}
--- a/OrthancServer/PrecompiledHeadersServer.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/PrecompiledHeadersServer.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/PrecompiledHeadersServer.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,42 +37,6 @@ #if ORTHANC_USE_PRECOMPILED_HEADERS == 1 -// DCMTK -#include <dcmtk/dcmdata/dcchrstr.h> -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmdata/dcdicent.h> -#include <dcmtk/dcmdata/dcdict.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcistrmf.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcostrmb.h> -#include <dcmtk/dcmdata/dcpixel.h> -#include <dcmtk/dcmdata/dcpixseq.h> -#include <dcmtk/dcmdata/dcpxitem.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcvrae.h> -#include <dcmtk/dcmdata/dcvras.h> -#include <dcmtk/dcmdata/dcvrcs.h> -#include <dcmtk/dcmdata/dcvrda.h> -#include <dcmtk/dcmdata/dcvrds.h> -#include <dcmtk/dcmdata/dcvrdt.h> -#include <dcmtk/dcmdata/dcvrfd.h> -#include <dcmtk/dcmdata/dcvrfl.h> -#include <dcmtk/dcmdata/dcvris.h> -#include <dcmtk/dcmdata/dcvrlo.h> -#include <dcmtk/dcmdata/dcvrlt.h> -#include <dcmtk/dcmdata/dcvrpn.h> -#include <dcmtk/dcmdata/dcvrsh.h> -#include <dcmtk/dcmdata/dcvrsl.h> -#include <dcmtk/dcmdata/dcvrss.h> -#include <dcmtk/dcmdata/dcvrst.h> -#include <dcmtk/dcmdata/dcvrtm.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcvrul.h> -#include <dcmtk/dcmdata/dcvrus.h> -#include <dcmtk/dcmdata/dcvrut.h> -#include <dcmtk/dcmnet/dcasccfg.h> -#include <dcmtk/dcmnet/diutil.h> +#include "ServerContext.h" #endif
--- a/OrthancServer/QueryRetrieveHandler.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/QueryRetrieveHandler.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +36,45 @@ #include "OrthancInitialization.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" +#include "../Core/Logging.h" + namespace Orthanc { + static void FixQueryLua(DicomMap& query, + ServerContext& context, + const std::string& modality) + { + static const char* LUA_CALLBACK = "OutgoingFindRequestFilter"; + + LuaScripting::Lock lock(context.GetLuaScripting()); + + if (lock.GetLua().IsExistingFunction(LUA_CALLBACK)) + { + LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK); + call.PushDicom(query); + call.PushJson(modality); + FromDcmtkBridge::ExecuteToDicom(query, call); + } + } + + + static void FixQueryBuiltin(DicomMap& query, + ModalityManufacturer manufacturer) + { + /** + * Introduce patches for specific manufacturers below. + **/ + + switch (manufacturer) + { + default: + break; + } + } + + void QueryRetrieveHandler::Invalidate() { done_ = false; @@ -49,8 +86,20 @@ { if (!done_) { - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - locker.GetConnection().Find(answers_, level_, query_); + // Firstly, fix the content of the query for specific manufacturers + DicomMap fixed; + fixed.Assign(query_); + FixQueryBuiltin(fixed, modality_.GetManufacturer()); + + // Secondly, possibly fix the query with the user-provider Lua callback + FixQueryLua(fixed, context_, modality_.GetApplicationEntityTitle()); + + { + DicomUserConnection connection(localAet_, modality_); + connection.Open(); + connection.Find(answers_, level_, fixed); + } + done_ = true; } } @@ -60,7 +109,8 @@ context_(context), localAet_(context.GetDefaultLocalApplicationEntityTitle()), done_(false), - level_(ResourceType_Study) + level_(ResourceType_Study), + answers_(false) { } @@ -84,50 +134,21 @@ const std::string& value) { Invalidate(); - query_.SetValue(tag, value); + query_.SetValue(tag, value, false); } - size_t QueryRetrieveHandler::GetAnswerCount() + size_t QueryRetrieveHandler::GetAnswersCount() { Run(); return answers_.GetSize(); } - const DicomMap& QueryRetrieveHandler::GetAnswer(size_t i) - { - Run(); - - if (i >= answers_.GetSize()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - return answers_.GetAnswer(i); - } - - - void QueryRetrieveHandler::Retrieve(const std::string& target, - size_t i) + void QueryRetrieveHandler::GetAnswer(DicomMap& target, + size_t i) { Run(); - - if (i >= answers_.GetSize()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - locker.GetConnection().Move(target, answers_.GetAnswer(i)); - } - - - void QueryRetrieveHandler::Retrieve(const std::string& target) - { - for (size_t i = 0; i < GetAnswerCount(); i++) - { - Retrieve(target, i); - } + answers_.GetAnswer(i).ExtractDicomSummary(target); } }
--- a/OrthancServer/QueryRetrieveHandler.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/QueryRetrieveHandler.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +41,7 @@ { private: ServerContext& context_; - const std::string& localAet_; + std::string localAet_; bool done_; RemoteModalityParameters modality_; ResourceType level_; @@ -50,17 +51,21 @@ void Invalidate(); - public: QueryRetrieveHandler(ServerContext& context); void SetModality(const std::string& symbolicName); - const RemoteModalityParameters& GetModality() const + const RemoteModalityParameters& GetRemoteModality() const { return modality_; } + const std::string& GetLocalAet() const + { + return localAet_; + } + const std::string& GetModalitySymbolicName() const { return modalityName_; @@ -83,13 +88,9 @@ void Run(); - size_t GetAnswerCount(); - - const DicomMap& GetAnswer(size_t i); + size_t GetAnswersCount(); - void Retrieve(const std::string& target, - size_t i); - - void Retrieve(const std::string& target); + void GetAnswer(DicomMap& target, + size_t i); }; }
--- a/OrthancServer/Scheduler/CallSystemCommand.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "CallSystemCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/Toolbox.h" -#include "../../Core/Uuid.h" - -namespace Orthanc -{ - CallSystemCommand::CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector<std::string>& arguments) : - context_(context), - command_(command), - arguments_(arguments) - { - } - - bool CallSystemCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Calling system command " << command_ << " on instance " << *it; - - try - { - std::string dicom; - context_.ReadFile(dicom, *it, FileContentType_Dicom); - - Toolbox::TemporaryFile tmp; - tmp.Write(dicom); - - std::vector<std::string> args = arguments_; - args.push_back(tmp.GetPath()); - - Toolbox::ExecuteSystemCommand(command_, args); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to call system command " << command_ - << " on instance " << *it << " in a Lua script: " << e.What(); - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/CallSystemCommand.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class CallSystemCommand : public IServerCommand - { - private: - ServerContext& context_; - std::string command_; - std::vector<std::string> arguments_; - - public: - CallSystemCommand(ServerContext& context, - const std::string& command, - const std::vector<std::string>& arguments); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/DeleteInstanceCommand.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DeleteInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - bool DeleteInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Deleting instance " << *it; - - try - { - Json::Value tmp; - context_.DeleteResource(tmp, *it, ResourceType_Instance); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to delete instance " << *it << " in a Lua script: " << e.What(); - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/DeleteInstanceCommand.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class DeleteInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - - public: - DeleteInstanceCommand(ServerContext& context) : context_(context) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/IServerCommand.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <list> -#include <string> -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class IServerCommand : public boost::noncopyable - { - public: - typedef std::list<std::string> ListOfStrings; - - virtual ~IServerCommand() - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) = 0; - }; -}
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ModifyInstanceCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification) : - context_(context), - origin_(origin), - modification_(modification) - { - modification_->SetAllowManualIdentifiers(true); - - if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) - { - modification_->SetLevel(ResourceType_Patient); - } - else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Study); - } - else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - modification_->SetLevel(ResourceType_Series); - } - else - { - modification_->SetLevel(ResourceType_Instance); - } - - if (origin_ != RequestOrigin_Lua) - { - // TODO If issued from HTTP, "remoteIp" and "username" must be provided - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - ModifyInstanceCommand::~ModifyInstanceCommand() - { - if (modification_) - { - delete modification_; - } - } - - - bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Modifying resource " << *it; - - try - { - std::auto_ptr<ParsedDicomFile> modified; - - { - ServerContext::DicomCacheLocker lock(context_, *it); - modified.reset(lock.GetDicom().Clone()); - } - - modification_->Apply(*modified); - - DicomInstanceToStore toStore; - assert(origin_ == RequestOrigin_Lua); - toStore.SetLuaOrigin(); - toStore.SetParsedDicomFile(*modified); - // TODO other metadata - toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it); - - std::string modifiedId; - context_.Store(modifiedId, toStore); - - // Only chain with other commands if this command succeeds - outputs.push_back(modifiedId); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to modify instance " << *it << " in a Lua script: " << e.What(); - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" -#include "../DicomModification.h" - -namespace Orthanc -{ - class ModifyInstanceCommand : public IServerCommand - { - private: - ServerContext& context_; - RequestOrigin origin_; - DicomModification* modification_; - - public: - ModifyInstanceCommand(ServerContext& context, - RequestOrigin origin, - DicomModification* modification); // takes the ownership - - virtual ~ModifyInstanceCommand(); - - const DicomModification& GetModification() const - { - return *modification_; - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/ServerCommandInstance.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ServerCommandInstance.h" - -#include "../../Core/OrthancException.h" - -namespace Orthanc -{ - bool ServerCommandInstance::Execute(IListener& listener) - { - ListOfStrings outputs; - - bool success = false; - - try - { - if (command_->Apply(outputs, inputs_)) - { - success = true; - } - } - catch (OrthancException&) - { - } - - if (!success) - { - listener.SignalFailure(jobId_); - return true; - } - - for (std::list<ServerCommandInstance*>::iterator - it = next_.begin(); it != next_.end(); ++it) - { - for (ListOfStrings::const_iterator - output = outputs.begin(); output != outputs.end(); ++output) - { - (*it)->AddInput(*output); - } - } - - listener.SignalSuccess(jobId_); - return true; - } - - - ServerCommandInstance::ServerCommandInstance(IServerCommand *command, - const std::string& jobId) : - command_(command), - jobId_(jobId), - connectedToSink_(false) - { - if (command_ == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - ServerCommandInstance::~ServerCommandInstance() - { - if (command_ != NULL) - { - delete command_; - } - } -}
--- a/OrthancServer/Scheduler/ServerCommandInstance.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/IDynamicObject.h" -#include "IServerCommand.h" - -namespace Orthanc -{ - class ServerCommandInstance : public IDynamicObject - { - friend class ServerScheduler; - - public: - class IListener - { - public: - virtual ~IListener() - { - } - - virtual void SignalSuccess(const std::string& jobId) = 0; - - virtual void SignalFailure(const std::string& jobId) = 0; - }; - - private: - typedef IServerCommand::ListOfStrings ListOfStrings; - - IServerCommand *command_; - std::string jobId_; - ListOfStrings inputs_; - std::list<ServerCommandInstance*> next_; - bool connectedToSink_; - - bool Execute(IListener& listener); - - public: - ServerCommandInstance(IServerCommand *command, - const std::string& jobId); - - virtual ~ServerCommandInstance(); - - const std::string& GetJobId() const - { - return jobId_; - } - - void AddInput(const std::string& input) - { - inputs_.push_back(input); - } - - void ConnectOutput(ServerCommandInstance& next) - { - next_.push_back(&next); - } - - void SetConnectedToSink(bool connected = true) - { - connectedToSink_ = connected; - } - - bool IsConnectedToSink() const - { - return connectedToSink_; - } - - const std::list<ServerCommandInstance*>& GetNextCommands() const - { - return next_; - } - }; -}
--- a/OrthancServer/Scheduler/ServerJob.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ServerJob.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Toolbox.h" -#include "../../Core/Uuid.h" - -namespace Orthanc -{ - void ServerJob::CheckOrdering() - { - std::map<ServerCommandInstance*, unsigned int> index; - - unsigned int count = 0; - for (std::list<ServerCommandInstance*>::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - index[*it] = count++; - } - - for (std::list<ServerCommandInstance*>::const_iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands(); - - for (std::list<ServerCommandInstance*>::const_iterator - next = nextCommands.begin(); next != nextCommands.end(); ++next) - { - if (index.find(*next) == index.end() || - index[*next] <= index[*it]) - { - // You must reorder your calls to "ServerJob::AddCommand" - throw OrthancException(ErrorCode_BadJobOrdering); - } - } - } - } - - - size_t ServerJob::Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener) - { - if (submitted_) - { - // This job has already been submitted - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - CheckOrdering(); - - size_t size = filters_.size(); - - for (std::list<ServerCommandInstance*>::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - target.Enqueue(*it); - } - - filters_.clear(); - submitted_ = true; - - return size; - } - - - ServerJob::ServerJob() : - jobId_(Toolbox::GenerateUuid()), - submitted_(false), - description_("no description") - { - } - - - ServerJob::~ServerJob() - { - for (std::list<ServerCommandInstance*>::iterator - it = filters_.begin(); it != filters_.end(); ++it) - { - delete *it; - } - - for (std::list<IDynamicObject*>::iterator - it = payloads_.begin(); it != payloads_.end(); ++it) - { - delete *it; - } - } - - - ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - filters_.push_back(new ServerCommandInstance(filter, jobId_)); - - return *filters_.back(); - } - - - IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload) - { - if (submitted_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - payloads_.push_back(payload); - - return *filters_.back(); - } - -}
--- a/OrthancServer/Scheduler/ServerJob.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerCommandInstance.h" -#include "../../Core/MultiThreading/SharedMessageQueue.h" - -namespace Orthanc -{ - class ServerJob - { - friend class ServerScheduler; - - private: - std::list<ServerCommandInstance*> filters_; - std::list<IDynamicObject*> payloads_; - std::string jobId_; - bool submitted_; - std::string description_; - - void CheckOrdering(); - - size_t Submit(SharedMessageQueue& target, - ServerCommandInstance::IListener& listener); - - public: - ServerJob(); - - ~ServerJob(); - - const std::string& GetId() const - { - return jobId_; - } - - void SetDescription(const std::string& description) - { - description_ = description; - } - - const std::string& GetDescription() const - { - return description_; - } - - ServerCommandInstance& AddCommand(IServerCommand* filter); - - // Take the ownership of a payload to a job. This payload will be - // automatically freed when the job succeeds or fails. - IDynamicObject& AddPayload(IDynamicObject* payload); - }; -}
--- a/OrthancServer/Scheduler/ServerScheduler.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,353 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ServerScheduler.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/Logging.h" - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class Sink : public IServerCommand - { - private: - ListOfStrings& target_; - - public: - Sink(ListOfStrings& target) : target_(target) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - target_.push_back(*it); - } - - return true; - } - }; - } - - - ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId) - { - Jobs::iterator info = jobs_.find(jobId); - - if (info == jobs_.end()) - { - throw OrthancException(ErrorCode_InternalError); - } - - return info->second; - } - - - void ServerScheduler::SignalSuccess(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.success_++; - - assert(info.failures_ == 0); - - if (info.success_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Success; - watchedJobFinished_.notify_all(); - } - - LOG(INFO) << "Job successfully finished (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::SignalFailure(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - JobInfo& info = GetJobInfo(jobId); - info.failures_++; - - if (info.success_ + info.failures_ >= info.size_) - { - if (info.watched_) - { - watchedJobStatus_[jobId] = JobStatus_Failure; - watchedJobFinished_.notify_all(); - } - - LOG(ERROR) << "Job has failed (" << info.description_ << ")"; - jobs_.erase(jobId); - - availableJob_.Release(); - } - } - - - void ServerScheduler::Worker(ServerScheduler* that) - { - static const int32_t TIMEOUT = 100; - - LOG(WARNING) << "The server scheduler has started"; - - while (!that->finish_) - { - std::auto_ptr<IDynamicObject> object(that->queue_.Dequeue(TIMEOUT)); - if (object.get() != NULL) - { - ServerCommandInstance& filter = dynamic_cast<ServerCommandInstance&>(*object); - - // Skip the execution of this filter if its parent job has - // previously failed. - bool jobHasFailed; - { - boost::mutex::scoped_lock lock(that->mutex_); - JobInfo& info = that->GetJobInfo(filter.GetJobId()); - jobHasFailed = (info.failures_ > 0 || info.cancel_); - } - - if (jobHasFailed) - { - that->SignalFailure(filter.GetJobId()); - } - else - { - filter.Execute(*that); - } - } - } - } - - - void ServerScheduler::SubmitInternal(ServerJob& job, - bool watched) - { - availableJob_.Acquire(); - - boost::mutex::scoped_lock lock(mutex_); - - JobInfo info; - info.size_ = job.Submit(queue_, *this); - info.cancel_ = false; - info.success_ = 0; - info.failures_ = 0; - info.description_ = job.GetDescription(); - info.watched_ = watched; - - assert(info.size_ > 0); - - if (watched) - { - watchedJobStatus_[job.GetId()] = JobStatus_Running; - } - - jobs_[job.GetId()] = info; - - LOG(INFO) << "New job submitted (" << job.description_ << ")"; - } - - - ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs) - { - finish_ = false; - worker_ = boost::thread(Worker, this); - } - - - ServerScheduler::~ServerScheduler() - { - if (!finish_) - { - LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - - void ServerScheduler::Stop() - { - if (!finish_) - { - finish_ = true; - - if (worker_.joinable()) - { - worker_.join(); - } - } - } - - - void ServerScheduler::Submit(ServerJob& job) - { - if (job.filters_.empty()) - { - return; - } - - SubmitInternal(job, false); - } - - - bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs, - ServerJob& job) - { - std::string jobId = job.GetId(); - - outputs.clear(); - - if (job.filters_.empty()) - { - return true; - } - - // Add a sink filter to collect all the results of the filters - // that have no next filter. - ServerCommandInstance& sink = job.AddCommand(new Sink(outputs)); - - for (std::list<ServerCommandInstance*>::iterator - it = job.filters_.begin(); it != job.filters_.end(); ++it) - { - if ((*it) != &sink && - (*it)->IsConnectedToSink()) - { - (*it)->ConnectOutput(sink); - } - } - - // Submit the job - SubmitInternal(job, true); - - // Wait for the job to complete (either success or failure) - JobStatus status; - - { - boost::mutex::scoped_lock lock(mutex_); - - assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end()); - - while (watchedJobStatus_[jobId] == JobStatus_Running) - { - watchedJobFinished_.wait(lock); - } - - status = watchedJobStatus_[jobId]; - watchedJobStatus_.erase(jobId); - } - - return (status == JobStatus_Success); - } - - - bool ServerScheduler::SubmitAndWait(ServerJob& job) - { - ListOfStrings ignoredSink; - return SubmitAndWait(ignoredSink, job); - } - - - bool ServerScheduler::IsRunning(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - return jobs_.find(jobId) != jobs_.end(); - } - - - void ServerScheduler::Cancel(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job != jobs_.end()) - { - job->second.cancel_ = true; - LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")"; - } - } - - - float ServerScheduler::GetProgress(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - Jobs::iterator job = jobs_.find(jobId); - - if (job == jobs_.end() || - job->second.size_ == 0 /* should never happen */) - { - // This job is not running - return 1; - } - - if (job->second.failures_ != 0) - { - return 1; - } - - if (job->second.size_ == 1) - { - return static_cast<float>(job->second.success_); - } - - return (static_cast<float>(job->second.success_) / - static_cast<float>(job->second.size_ - 1)); - } - - - void ServerScheduler::GetListOfJobs(ListOfStrings& jobs) - { - boost::mutex::scoped_lock lock(mutex_); - - jobs.clear(); - - for (Jobs::const_iterator - it = jobs_.begin(); it != jobs_.end(); ++it) - { - jobs.push_back(it->first); - } - } -}
--- a/OrthancServer/Scheduler/ServerScheduler.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerJob.h" - -#include "../../Core/MultiThreading/Semaphore.h" - -namespace Orthanc -{ - class ServerScheduler : public ServerCommandInstance::IListener - { - private: - struct JobInfo - { - bool watched_; - bool cancel_; - size_t size_; - size_t success_; - size_t failures_; - std::string description_; - }; - - enum JobStatus - { - JobStatus_Running = 1, - JobStatus_Success = 2, - JobStatus_Failure = 3 - }; - - typedef IServerCommand::ListOfStrings ListOfStrings; - typedef std::map<std::string, JobInfo> Jobs; - - boost::mutex mutex_; - boost::condition_variable watchedJobFinished_; - Jobs jobs_; - SharedMessageQueue queue_; - bool finish_; - boost::thread worker_; - std::map<std::string, JobStatus> watchedJobStatus_; - Semaphore availableJob_; - - JobInfo& GetJobInfo(const std::string& jobId); - - virtual void SignalSuccess(const std::string& jobId); - - virtual void SignalFailure(const std::string& jobId); - - static void Worker(ServerScheduler* that); - - void SubmitInternal(ServerJob& job, - bool watched); - - public: - ServerScheduler(unsigned int maxjobs); - - ~ServerScheduler(); - - void Stop(); - - void Submit(ServerJob& job); - - bool SubmitAndWait(ListOfStrings& outputs, - ServerJob& job); - - bool SubmitAndWait(ServerJob& job); - - bool IsRunning(const std::string& jobId); - - void Cancel(const std::string& jobId); - - // Returns a number between 0 and 1 - float GetProgress(const std::string& jobId); - - bool IsRunning(const ServerJob& job) - { - return IsRunning(job.GetId()); - } - - void Cancel(const ServerJob& job) - { - Cancel(job.GetId()); - } - - float GetProgress(const ServerJob& job) - { - return GetProgress(job.GetId()); - } - - void GetListOfJobs(ListOfStrings& jobs); - }; -}
--- a/OrthancServer/Scheduler/StorePeerCommand.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "StorePeerCommand.h" - -#include "../../Core/Logging.h" -#include "../../Core/HttpClient.h" - -namespace Orthanc -{ - StorePeerCommand::StorePeerCommand(ServerContext& context, - const OrthancPeerParameters& peer, - bool ignoreExceptions) : - context_(context), - peer_(peer), - ignoreExceptions_(ignoreExceptions) - { - } - - bool StorePeerCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - // Configure the HTTP client - HttpClient client; - client.SetProxy(Configuration::GetGlobalStringParameter("HttpProxy", "")); - if (peer_.GetUsername().size() != 0 && - peer_.GetPassword().size() != 0) - { - client.SetCredentials(peer_.GetUsername().c_str(), - peer_.GetPassword().c_str()); - } - - client.SetUrl(peer_.GetUrl() + "instances"); - client.SetMethod(HttpMethod_Post); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to peer \"" - << peer_.GetUrl() << "\""; - - try - { - context_.ReadFile(client.GetBody(), *it, FileContentType_Dicom); - - std::string answer; - if (!client.Apply(answer)) - { - LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\""; - throw OrthancException(ErrorCode_NetworkProtocol); - } - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Unable to forward to an Orthanc peer in a Lua script (instance " - << *it << ", peer " << peer_.GetUrl() << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/StorePeerCommand.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" -#include "../OrthancInitialization.h" - -namespace Orthanc -{ - class StorePeerCommand : public IServerCommand - { - private: - ServerContext& context_; - OrthancPeerParameters peer_; - bool ignoreExceptions_; - - public: - StorePeerCommand(ServerContext& context, - const OrthancPeerParameters& peer, - bool ignoreExceptions); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- a/OrthancServer/Scheduler/StoreScuCommand.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "StoreScuCommand.h" - -#include "../../Core/Logging.h" - -namespace Orthanc -{ - StoreScuCommand::StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions) : - context_(context), - modality_(modality), - ignoreExceptions_(ignoreExceptions), - localAet_(localAet) - { - } - - bool StoreScuCommand::Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); - - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to modality \"" - << modality_.GetApplicationEntityTitle() << "\""; - - try - { - std::string dicom; - context_.ReadFile(dicom, *it, FileContentType_Dicom); - locker.GetConnection().Store(dicom); - - // Only chain with other commands if this command succeeds - outputs.push_back(*it); - } - catch (OrthancException& e) - { - // Ignore transmission errors (e.g. if the remote modality is - // powered off) - LOG(ERROR) << "Unable to forward to a modality in a Lua script (instance " - << *it << "): " << e.What(); - - if (!ignoreExceptions_) - { - throw; - } - } - } - - return true; - } -}
--- a/OrthancServer/Scheduler/StoreScuCommand.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IServerCommand.h" -#include "../ServerContext.h" - -namespace Orthanc -{ - class StoreScuCommand : public IServerCommand - { - private: - ServerContext& context_; - RemoteModalityParameters modality_; - bool ignoreExceptions_; - std::string localAet_; - - public: - StoreScuCommand(ServerContext& context, - const std::string& localAet, - const RemoteModalityParameters& modality, - bool ignoreExceptions); - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs); - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Search/HierarchicalMatcher.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,342 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "HierarchicalMatcher.h" + +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/DicomParsing/ToDcmtkBridge.h" +#include "../OrthancInitialization.h" + +#include <dcmtk/dcmdata/dcfilefo.h> + +namespace Orthanc +{ + HierarchicalMatcher::HierarchicalMatcher(ParsedDicomFile& query) + { + Setup(*query.GetDcmtkObject().getDataset(), + Configuration::GetGlobalBoolParameter("CaseSensitivePN", false), + query.GetEncoding()); + } + + + HierarchicalMatcher::~HierarchicalMatcher() + { + for (Constraints::iterator it = constraints_.begin(); + it != constraints_.end(); ++it) + { + if (it->second != NULL) + { + delete it->second; + } + } + + for (Sequences::iterator it = sequences_.begin(); + it != sequences_.end(); ++it) + { + if (it->second != NULL) + { + delete it->second; + } + } + } + + + void HierarchicalMatcher::Setup(DcmItem& dataset, + bool caseSensitivePN, + Encoding encoding) + { + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); + if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET || // Ignore encoding + tag.GetElement() == 0x0000) // Ignore all "Group Length" tags + { + continue; + } + + ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); + + if (constraints_.find(tag) != constraints_.end() || + sequences_.find(tag) != sequences_.end()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + if (vr == ValueRepresentation_Sequence) + { + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + + if (sequence.card() == 0 || + (sequence.card() == 1 && sequence.getItem(0)->card() == 0)) + { + // Universal matching of a sequence + sequences_[tag] = NULL; + } + else if (sequence.card() == 1) + { + sequences_[tag] = new HierarchicalMatcher(*sequence.getItem(0), caseSensitivePN, encoding); + } + else + { + throw OrthancException(ErrorCode_BadRequest); + } + } + else + { + std::set<DicomTag> ignoreTagLength; + std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_None, + ORTHANC_MAXIMUM_TAG_LENGTH, encoding, ignoreTagLength)); + + if (value->IsBinary()) + { + if (!value->GetContent().empty()) + { + LOG(WARNING) << "This C-Find modality worklist query contains a non-empty tag (" + << tag.Format() << ") with UN (unknown) value representation. " + << "It will be ignored."; + } + + constraints_[tag] = NULL; + } + else if (value->IsNull() || + value->GetContent().empty()) + { + // This is an universal matcher + constraints_[tag] = NULL; + } + else + { + // DICOM specifies that searches must be case sensitive, except + // for tags with a PN value representation + bool sensitive = true; + if (vr == ValueRepresentation_PersonName) + { + sensitive = caseSensitivePN; + } + + constraints_[tag] = IFindConstraint::ParseDicomConstraint(tag, value->GetContent(), sensitive); + } + } + } + } + + + std::string HierarchicalMatcher::Format(const std::string& prefix) const + { + std::string s; + + for (Constraints::const_iterator it = constraints_.begin(); + it != constraints_.end(); ++it) + { + s += prefix + it->first.Format() + " "; + + if (it->second == NULL) + { + s += "*\n"; + } + else + { + s += it->second->Format() + "\n"; + } + } + + for (Sequences::const_iterator it = sequences_.begin(); + it != sequences_.end(); ++it) + { + s += prefix + it->first.Format() + " "; + + if (it->second == NULL) + { + s += "*\n"; + } + else + { + s += "Sequence:\n" + it->second->Format(prefix + " "); + } + } + + return s; + } + + + bool HierarchicalMatcher::Match(ParsedDicomFile& dicom) const + { + return MatchInternal(*dicom.GetDcmtkObject().getDataset(), + dicom.GetEncoding()); + } + + + bool HierarchicalMatcher::MatchInternal(DcmItem& item, + Encoding encoding) const + { + for (Constraints::const_iterator it = constraints_.begin(); + it != constraints_.end(); ++it) + { + if (it->second != NULL) + { + DcmTagKey tag = ToDcmtkBridge::Convert(it->first); + + DcmElement* element = NULL; + if (!item.findAndGetElement(tag, element).good() || + element == NULL) + { + return false; + } + + std::set<DicomTag> ignoreTagLength; + std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_None, + ORTHANC_MAXIMUM_TAG_LENGTH, encoding, ignoreTagLength)); + + if (value->IsNull() || + value->IsBinary() || + !it->second->Match(value->GetContent())) + { + return false; + } + } + } + + for (Sequences::const_iterator it = sequences_.begin(); + it != sequences_.end(); ++it) + { + if (it->second != NULL) + { + DcmTagKey tag = ToDcmtkBridge::Convert(it->first); + + DcmSequenceOfItems* sequence = NULL; + if (!item.findAndGetSequence(tag, sequence).good() || + sequence == NULL) + { + continue; + } + + bool match = false; + + for (unsigned long i = 0; i < sequence->card(); i++) + { + if (it->second->MatchInternal(*sequence->getItem(i), encoding)) + { + match = true; + break; + } + } + + if (!match) + { + return false; + } + } + } + + return true; + } + + + DcmDataset* HierarchicalMatcher::ExtractInternal(DcmItem& source, + Encoding encoding) const + { + std::auto_ptr<DcmDataset> target(new DcmDataset); + + for (Constraints::const_iterator it = constraints_.begin(); + it != constraints_.end(); ++it) + { + DcmTagKey tag = ToDcmtkBridge::Convert(it->first); + + DcmElement* element = NULL; + if (source.findAndGetElement(tag, element).good() && + element != NULL) + { + std::auto_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(it->first)); + cloned->copyFrom(*element); + target->insert(cloned.release()); + } + } + + for (Sequences::const_iterator it = sequences_.begin(); + it != sequences_.end(); ++it) + { + DcmTagKey tag = ToDcmtkBridge::Convert(it->first); + + DcmSequenceOfItems* sequence = NULL; + if (source.findAndGetSequence(tag, sequence).good() && + sequence != NULL) + { + std::auto_ptr<DcmSequenceOfItems> cloned(new DcmSequenceOfItems(tag)); + + for (unsigned long i = 0; i < sequence->card(); i++) + { + if (it->second == NULL) + { + cloned->append(new DcmItem(*sequence->getItem(i))); + } + else if (it->second->MatchInternal(*sequence->getItem(i), encoding)) // TODO Might be optimized + { + // It is necessary to encapsulate the child dataset into a + // "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)); + cloned->append(new DcmItem(*child)); + } + } + + target->insert(cloned.release()); + } + } + + return target.release(); + } + + + ParsedDicomFile* HierarchicalMatcher::Extract(ParsedDicomFile& dicom) const + { + std::auto_ptr<DcmDataset> dataset(ExtractInternal(*dicom.GetDcmtkObject().getDataset(), + dicom.GetEncoding())); + + std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(*dataset)); + result->SetEncoding(dicom.GetEncoding()); + + return result.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Search/HierarchicalMatcher.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IFindConstraint.h" +#include "../../Core/DicomParsing/ParsedDicomFile.h" + +class DcmItem; + +namespace Orthanc +{ + class HierarchicalMatcher : public boost::noncopyable + { + private: + typedef std::map<DicomTag, IFindConstraint*> Constraints; + typedef std::map<DicomTag, HierarchicalMatcher*> Sequences; + + Constraints constraints_; + Sequences sequences_; + + void Setup(DcmItem& query, + bool caseSensitivePN, + Encoding encoding); + + HierarchicalMatcher(DcmItem& query, + bool caseSensitivePN, + Encoding encoding) + { + Setup(query, caseSensitivePN, encoding); + } + + bool MatchInternal(DcmItem& dicom, + Encoding encoding) const; + + DcmDataset* ExtractInternal(DcmItem& dicom, + Encoding encoding) const; + + public: + HierarchicalMatcher(ParsedDicomFile& query); + + ~HierarchicalMatcher(); + + std::string Format(const std::string& prefix = "") const; + + bool Match(ParsedDicomFile& dicom) const; + + ParsedDicomFile* Extract(ParsedDicomFile& dicom) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Search/IFindConstraint.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,131 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IFindConstraint.h" + +#include "ListConstraint.h" +#include "RangeConstraint.h" +#include "ValueConstraint.h" +#include "WildcardConstraint.h" + +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/OrthancException.h" + +namespace Orthanc +{ + IFindConstraint* IFindConstraint::ParseDicomConstraint(const DicomTag& tag, + const std::string& dicomQuery, + bool caseSensitive) + { + ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); + + if (vr == ValueRepresentation_Sequence) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if ((vr == ValueRepresentation_Date || + vr == ValueRepresentation_DateTime || + vr == ValueRepresentation_Time) && + dicomQuery.find('-') != std::string::npos) + { + /** + * Range matching is only defined for TM, DA and DT value + * representations. This code fixes issues 35 and 37. + * + * Reference: "Range matching is not defined for types of + * Attributes other than dates and times", DICOM PS 3.4, + * C.2.2.2.5 ("Range Matching"). + **/ + size_t separator = dicomQuery.find('-'); + std::string lower = dicomQuery.substr(0, separator); + std::string upper = dicomQuery.substr(separator + 1); + return new RangeConstraint(lower, upper, caseSensitive); + } + else if (dicomQuery.find('\\') != std::string::npos) + { + std::auto_ptr<ListConstraint> constraint(new ListConstraint(caseSensitive)); + + std::vector<std::string> items; + Toolbox::TokenizeString(items, dicomQuery, '\\'); + + for (size_t i = 0; i < items.size(); i++) + { + constraint->AddAllowedValue(items[i]); + } + + return constraint.release(); + } + else if (dicomQuery.find('*') != std::string::npos || + dicomQuery.find('?') != std::string::npos) + { + return new WildcardConstraint(dicomQuery, caseSensitive); + } + else + { + /** + * Case-insensitive match for PN value representation (Patient + * Name). Case-senstive match for all the other value + * representations. + * + * Reference: DICOM PS 3.4 + * - C.2.2.2.1 ("Single Value Matching") + * - C.2.2.2.4 ("Wild Card Matching") + * http://medical.nema.org/Dicom/2011/11_04pu.pdf + * + * "Except for Attributes with a PN Value Representation, only + * entities with values which match exactly the value specified in the + * request shall match. This matching is case-sensitive, i.e., + * sensitive to the exact encoding of the key attribute value in + * character sets where a letter may have multiple encodings (e.g., + * based on its case, its position in a word, or whether it is + * accented) + * + * For Attributes with a PN Value Representation (e.g., Patient Name + * (0010,0010)), an application may perform literal matching that is + * either case-sensitive, or that is insensitive to some or all + * aspects of case, position, accent, or other character encoding + * variants." + * + * (0008,0018) UI SOPInstanceUID => Case-sensitive + * (0008,0050) SH AccessionNumber => Case-sensitive + * (0010,0020) LO PatientID => Case-sensitive + * (0020,000D) UI StudyInstanceUID => Case-sensitive + * (0020,000E) UI SeriesInstanceUID => Case-sensitive + **/ + + return new ValueConstraint(dicomQuery, caseSensitive); + } + } +}
--- a/OrthancServer/Search/IFindConstraint.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/IFindConstraint.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,5 +50,11 @@ const DicomTag& tag) const = 0; virtual bool Match(const std::string& value) const = 0; + + virtual std::string Format() const = 0; + + static IFindConstraint* ParseDicomConstraint(const DicomTag& tag, + const std::string& dicomQuery, + bool caseSensitive); }; }
--- a/OrthancServer/Search/ListConstraint.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/ListConstraint.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -44,9 +45,7 @@ } else { - std::string s = value; - Toolbox::ToUpperCase(s); - allowedValues_.insert(s); + allowedValues_.insert(Toolbox::ToUpperCaseWithAccents(value)); } } @@ -66,13 +65,36 @@ bool ListConstraint::Match(const std::string& value) const { - std::string v = value; - - if (!isCaseSensitive_) + std::string s; + + if (isCaseSensitive_) { - Toolbox::ToUpperCase(v); + s = value; + } + else + { + s = Toolbox::ToUpperCaseWithAccents(value); } - return allowedValues_.find(v) != allowedValues_.end(); + return allowedValues_.find(s) != allowedValues_.end(); + } + + + std::string ListConstraint::Format() const + { + std::string s; + + for (std::set<std::string>::const_iterator + it = allowedValues_.begin(); it != allowedValues_.end(); ++it) + { + if (!s.empty()) + { + s += "\\"; + } + + s += *it; + } + + return s; } }
--- a/OrthancServer/Search/ListConstraint.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/ListConstraint.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -67,5 +68,7 @@ const DicomTag& tag) const; virtual bool Match(const std::string& value) const; + + virtual std::string Format() const; }; }
--- a/OrthancServer/Search/LookupIdentifierQuery.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/LookupIdentifierQuery.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +36,7 @@ #include "../../Core/OrthancException.h" #include "SetOfResources.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include <cassert> @@ -43,72 +44,16 @@ namespace Orthanc { - static const DicomTag patientIdentifiers[] = - { - DICOM_TAG_PATIENT_ID, - DICOM_TAG_PATIENT_NAME, - DICOM_TAG_PATIENT_BIRTH_DATE - }; - - static const DicomTag studyIdentifiers[] = - { - DICOM_TAG_PATIENT_ID, - DICOM_TAG_PATIENT_NAME, - DICOM_TAG_PATIENT_BIRTH_DATE, - DICOM_TAG_STUDY_INSTANCE_UID, - DICOM_TAG_ACCESSION_NUMBER, - DICOM_TAG_STUDY_DESCRIPTION, - DICOM_TAG_STUDY_DATE - }; - - static const DicomTag seriesIdentifiers[] = - { - DICOM_TAG_SERIES_INSTANCE_UID - }; - - static const DicomTag instanceIdentifiers[] = - { - DICOM_TAG_SOP_INSTANCE_UID - }; - - - void LookupIdentifierQuery::LoadIdentifiers(const DicomTag*& tags, - size_t& size, - ResourceType level) - { - switch (level) - { - case ResourceType_Patient: - tags = patientIdentifiers; - size = sizeof(patientIdentifiers) / sizeof(DicomTag); - break; - - case ResourceType_Study: - tags = studyIdentifiers; - size = sizeof(studyIdentifiers) / sizeof(DicomTag); - break; - - case ResourceType_Series: - tags = seriesIdentifiers; - size = sizeof(seriesIdentifiers) / sizeof(DicomTag); - break; - - case ResourceType_Instance: - tags = instanceIdentifiers; - size = sizeof(instanceIdentifiers) / sizeof(DicomTag); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - LookupIdentifierQuery::Disjunction::~Disjunction() { - for (size_t i = 0; i < disjunction_.size(); i++) + for (size_t i = 0; i < singleConstraints_.size(); i++) { - delete disjunction_[i]; + delete singleConstraints_[i]; + } + + for (size_t i = 0; i < rangeConstraints_.size(); i++) + { + delete rangeConstraints_[i]; } } @@ -117,87 +62,52 @@ IdentifierConstraintType type, const std::string& value) { - disjunction_.push_back(new Constraint(tag, type, value)); + singleConstraints_.push_back(new SingleConstraint(tag, type, value)); + } + + + void LookupIdentifierQuery::Disjunction::AddRange(const DicomTag& tag, + const std::string& start, + const std::string& end) + { + rangeConstraints_.push_back(new RangeConstraint(tag, start, end)); } LookupIdentifierQuery::~LookupIdentifierQuery() { - for (Constraints::iterator it = constraints_.begin(); - it != constraints_.end(); ++it) + for (Disjunctions::iterator it = disjunctions_.begin(); + it != disjunctions_.end(); ++it) { delete *it; } } - - bool LookupIdentifierQuery::IsIdentifier(const DicomTag& tag, - ResourceType level) - { - const DicomTag* tags; - size_t size; - - LoadIdentifiers(tags, size, level); - - for (size_t i = 0; i < size; i++) - { - if (tag == tags[i]) - { - return true; - } - } - - return false; - } - - void LookupIdentifierQuery::AddConstraint(DicomTag tag, IdentifierConstraintType type, const std::string& value) { assert(IsIdentifier(tag)); - constraints_.push_back(new Disjunction); - constraints_.back()->Add(tag, type, value); + disjunctions_.push_back(new Disjunction); + disjunctions_.back()->Add(tag, type, value); + } + + + void LookupIdentifierQuery::AddRange(DicomTag tag, + const std::string& start, + const std::string& end) + { + assert(IsIdentifier(tag)); + disjunctions_.push_back(new Disjunction); + disjunctions_.back()->AddRange(tag, start, end); } LookupIdentifierQuery::Disjunction& LookupIdentifierQuery::AddDisjunction() { - constraints_.push_back(new Disjunction); - return *constraints_.back(); - } - - - std::string LookupIdentifierQuery::NormalizeIdentifier(const std::string& value) - { - std::string s = Toolbox::ConvertToAscii(Toolbox::StripSpaces(value)); - Toolbox::ToUpperCase(s); - return s; - } - - - void LookupIdentifierQuery::StoreIdentifiers(IDatabaseWrapper& database, - int64_t resource, - ResourceType level, - const DicomMap& map) - { - const DicomTag* tags; - size_t size; - - LoadIdentifiers(tags, size, level); - - for (size_t i = 0; i < size; i++) - { - const DicomValue* value = map.TestAndGetValue(tags[i]); - if (value != NULL && - !value->IsNull() && - !value->IsBinary()) - { - std::string s = NormalizeIdentifier(value->GetContent()); - database.SetIdentifierTag(resource, tags[i], s); - } - } + disjunctions_.push_back(new Disjunction); + return *disjunctions_.back(); } @@ -214,15 +124,26 @@ void LookupIdentifierQuery::Apply(SetOfResources& result, IDatabaseWrapper& database) { - for (size_t i = 0; i < GetSize(); i++) + for (size_t i = 0; i < disjunctions_.size(); i++) { std::list<int64_t> a; - for (size_t j = 0; j < constraints_[i]->GetSize(); j++) + for (size_t j = 0; j < disjunctions_[i]->GetSingleConstraintsCount(); j++) { - const Constraint& constraint = constraints_[i]->GetConstraint(j); + const SingleConstraint& constraint = disjunctions_[i]->GetSingleConstraint(j); std::list<int64_t> b; - database.LookupIdentifier(b, level_, constraint.GetTag(), constraint.GetType(), constraint.GetValue()); + database.LookupIdentifier(b, level_, constraint.GetTag(), + constraint.GetType(), constraint.GetValue()); + + a.splice(a.end(), b); + } + + for (size_t j = 0; j < disjunctions_[i]->GetRangeConstraintsCount(); j++) + { + const RangeConstraint& constraint = disjunctions_[i]->GetRangeConstraint(j); + std::list<int64_t> b; + database.LookupIdentifierRange(b, level_, constraint.GetTag(), + constraint.GetStart(), constraint.GetEnd()); a.splice(a.end(), b); } @@ -235,18 +156,18 @@ void LookupIdentifierQuery::Print(std::ostream& s) const { s << "Constraint: " << std::endl; - for (Constraints::const_iterator - it = constraints_.begin(); it != constraints_.end(); ++it) + for (Disjunctions::const_iterator + it = disjunctions_.begin(); it != disjunctions_.end(); ++it) { - if (it == constraints_.begin()) + if (it == disjunctions_.begin()) s << " "; else s << "OR "; - for (size_t j = 0; j < (*it)->GetSize(); j++) + for (size_t j = 0; j < (*it)->GetSingleConstraintsCount(); j++) { - const Constraint& c = (*it)->GetConstraint(j); - s << FromDcmtkBridge::GetName(c.GetTag()); + const SingleConstraint& c = (*it)->GetSingleConstraint(j); + s << FromDcmtkBridge::GetTagName(c.GetTag(), ""); switch (c.GetType()) {
--- a/OrthancServer/Search/LookupIdentifierQuery.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/LookupIdentifierQuery.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +33,7 @@ #pragma once -#include "../ServerEnumerations.h" +#include "../ServerToolbox.h" #include "../IDatabaseWrapper.h" #include "SetOfResources.h" @@ -64,8 +65,11 @@ class LookupIdentifierQuery : public boost::noncopyable { + // This class encodes a conjunction ("AND") of disjunctions. Each + // disjunction represents an "OR" of several constraints. + public: - class Constraint + class SingleConstraint { private: DicomTag tag_; @@ -73,12 +77,12 @@ std::string value_; public: - Constraint(const DicomTag& tag, - IdentifierConstraintType type, - const std::string& value) : + SingleConstraint(const DicomTag& tag, + IdentifierConstraintType type, + const std::string& value) : tag_(tag), type_(type), - value_(NormalizeIdentifier(value)) + value_(ServerToolbox::NormalizeIdentifier(value)) { } @@ -99,10 +103,45 @@ }; + class RangeConstraint + { + private: + DicomTag tag_; + std::string start_; + std::string end_; + + public: + RangeConstraint(const DicomTag& tag, + const std::string& start, + const std::string& end) : + tag_(tag), + start_(ServerToolbox::NormalizeIdentifier(start)), + end_(ServerToolbox::NormalizeIdentifier(end)) + { + } + + const DicomTag& GetTag() const + { + return tag_; + } + + const std::string& GetStart() const + { + return start_; + } + + const std::string& GetEnd() const + { + return end_; + } + }; + + class Disjunction : public boost::noncopyable { private: - std::vector<Constraint*> disjunction_; + std::vector<SingleConstraint*> singleConstraints_; + std::vector<RangeConstraint*> rangeConstraints_; public: ~Disjunction(); @@ -111,25 +150,37 @@ IdentifierConstraintType type, const std::string& value); - size_t GetSize() const + void AddRange(const DicomTag& tag, + const std::string& start, + const std::string& end); + + size_t GetSingleConstraintsCount() const { - return disjunction_.size(); + return singleConstraints_.size(); } - const Constraint& GetConstraint(size_t i) const + const SingleConstraint& GetSingleConstraint(size_t i) const + { + return *singleConstraints_[i]; + } + + size_t GetRangeConstraintsCount() const { - return *disjunction_[i]; + return rangeConstraints_.size(); + } + + const RangeConstraint& GetRangeConstraint(size_t i) const + { + return *rangeConstraints_[i]; } }; private: - typedef std::vector<Disjunction*> Constraints; + typedef std::vector<Disjunction*> Disjunctions; ResourceType level_; - Constraints constraints_; - - static std::string NormalizeIdentifier(const std::string& value); + Disjunctions disjunctions_; public: LookupIdentifierQuery(ResourceType level) : level_(level) @@ -140,13 +191,17 @@ bool IsIdentifier(const DicomTag& tag) { - return IsIdentifier(tag, level_); + return ServerToolbox::IsIdentifier(tag, level_); } void AddConstraint(DicomTag tag, IdentifierConstraintType type, const std::string& value); + void AddRange(DicomTag tag, + const std::string& start, + const std::string& end); + Disjunction& AddDisjunction(); ResourceType GetLevel() const @@ -154,11 +209,6 @@ return level_; } - size_t GetSize() const - { - return constraints_.size(); - } - // The database must be locked void Apply(std::list<std::string>& result, IDatabaseWrapper& database); @@ -166,18 +216,6 @@ void Apply(SetOfResources& result, IDatabaseWrapper& database); - static void LoadIdentifiers(const DicomTag*& tags, - size_t& size, - ResourceType level); - - static bool IsIdentifier(const DicomTag& tag, - ResourceType level); - - static void StoreIdentifiers(IDatabaseWrapper& database, - int64_t resource, - ResourceType level, - const DicomMap& map); - void Print(std::ostream& s) const; }; }
--- a/OrthancServer/Search/LookupResource.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/LookupResource.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +34,10 @@ #include "../PrecompiledHeadersServer.h" #include "LookupResource.h" -#include "ListConstraint.h" -#include "RangeConstraint.h" -#include "ValueConstraint.h" -#include "WildcardConstraint.h" - #include "../../Core/OrthancException.h" #include "../../Core/FileStorage/StorageAccessor.h" #include "../ServerToolbox.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" namespace Orthanc @@ -51,7 +47,7 @@ const DicomTag* tags = NULL; size_t size; - LookupIdentifierQuery::LoadIdentifiers(tags, size, level); + ServerToolbox::LoadIdentifiers(tags, size, level); for (size_t i = 0; i < size; i++) { @@ -130,16 +126,16 @@ levels_[ResourceType_Patient] = new Level(ResourceType_Patient); break; - case ResourceType_Study: - levels_[ResourceType_Study] = new Level(ResourceType_Study); + case ResourceType_Instance: + levels_[ResourceType_Instance] = new Level(ResourceType_Instance); // Do not add "break" here case ResourceType_Series: levels_[ResourceType_Series] = new Level(ResourceType_Series); // Do not add "break" here - case ResourceType_Instance: - levels_[ResourceType_Instance] = new Level(ResourceType_Instance); + case ResourceType_Study: + levels_[ResourceType_Study] = new Level(ResourceType_Study); break; default: @@ -426,85 +422,16 @@ const std::string& dicomQuery, bool caseSensitive) { - ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag); - // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html if (tag == DICOM_TAG_MODALITIES_IN_STUDY) { SetModalitiesInStudy(dicomQuery); } - else if ((vr == ValueRepresentation_Date || - vr == ValueRepresentation_DateTime || - vr == ValueRepresentation_Time) && - dicomQuery.find('-') != std::string::npos) - { - /** - * Range matching is only defined for TM, DA and DT value - * representations. This code fixes issues 35 and 37. - * - * Reference: "Range matching is not defined for types of - * Attributes other than dates and times", DICOM PS 3.4, - * C.2.2.2.5 ("Range Matching"). - **/ - size_t separator = dicomQuery.find('-'); - std::string lower = dicomQuery.substr(0, separator); - std::string upper = dicomQuery.substr(separator + 1); - Add(tag, new RangeConstraint(lower, upper, caseSensitive)); - } - else if (dicomQuery.find('\\') != std::string::npos) - { - std::auto_ptr<ListConstraint> constraint(new ListConstraint(caseSensitive)); - - std::vector<std::string> items; - Toolbox::TokenizeString(items, dicomQuery, '\\'); - - for (size_t i = 0; i < items.size(); i++) - { - constraint->AddAllowedValue(items[i]); - } - - Add(tag, constraint.release()); - } - else if (dicomQuery.find('*') != std::string::npos || - dicomQuery.find('?') != std::string::npos) + else { - Add(tag, new WildcardConstraint(dicomQuery, caseSensitive)); - } - else - { - /** - * Case-insensitive match for PN value representation (Patient - * Name). Case-senstive match for all the other value - * representations. - * - * Reference: DICOM PS 3.4 - * - C.2.2.2.1 ("Single Value Matching") - * - C.2.2.2.4 ("Wild Card Matching") - * http://medical.nema.org/Dicom/2011/11_04pu.pdf - * - * "Except for Attributes with a PN Value Representation, only - * entities with values which match exactly the value specified in the - * request shall match. This matching is case-sensitive, i.e., - * sensitive to the exact encoding of the key attribute value in - * character sets where a letter may have multiple encodings (e.g., - * based on its case, its position in a word, or whether it is - * accented) - * - * For Attributes with a PN Value Representation (e.g., Patient Name - * (0010,0010)), an application may perform literal matching that is - * either case-sensitive, or that is insensitive to some or all - * aspects of case, position, accent, or other character encoding - * variants." - * - * (0008,0018) UI SOPInstanceUID => Case-sensitive - * (0008,0050) SH AccessionNumber => Case-sensitive - * (0010,0020) LO PatientID => Case-sensitive - * (0020,000D) UI StudyInstanceUID => Case-sensitive - * (0020,000E) UI SeriesInstanceUID => Case-sensitive - **/ - - Add(tag, new ValueConstraint(dicomQuery, caseSensitive)); + Add(tag, IFindConstraint::ParseDicomConstraint(tag, dicomQuery, caseSensitive)); } } + }
--- a/OrthancServer/Search/LookupResource.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/LookupResource.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/RangeConstraint.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/RangeConstraint.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,14 +41,17 @@ RangeConstraint::RangeConstraint(const std::string& lower, const std::string& upper, bool isCaseSensitive) : - lower_(lower), - upper_(upper), isCaseSensitive_(isCaseSensitive) { - if (!isCaseSensitive_) + if (isCaseSensitive_) { - Toolbox::ToUpperCase(lower_); - Toolbox::ToUpperCase(upper_); + lower_ = lower; + upper_ = upper; + } + else + { + lower_ = Toolbox::ToUpperCaseWithAccents(lower); + upper_ = Toolbox::ToUpperCaseWithAccents(upper); } } @@ -55,18 +59,37 @@ void RangeConstraint::Setup(LookupIdentifierQuery& lookup, const DicomTag& tag) const { - lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_); - lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_); + if (!lower_.empty() && + !upper_.empty()) + { + lookup.AddRange(tag, lower_, upper_); + } + else + { + if (!lower_.empty()) + { + lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_); + } + + if (!upper_.empty()) + { + lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_); + } + } } bool RangeConstraint::Match(const std::string& value) const { - std::string v = value; + std::string v; - if (!isCaseSensitive_) + if (isCaseSensitive_) { - Toolbox::ToUpperCase(v); + v = value; + } + else + { + v = Toolbox::ToUpperCaseWithAccents(value); } if (lower_.size() == 0 &&
--- a/OrthancServer/Search/RangeConstraint.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/RangeConstraint.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -64,5 +65,10 @@ const DicomTag& tag) const; virtual bool Match(const std::string& value) const; + + virtual std::string Format() const + { + return lower_ + "-" + upper_; + } }; }
--- a/OrthancServer/Search/SetOfResources.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/SetOfResources.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/SetOfResources.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/SetOfResources.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/ValueConstraint.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/ValueConstraint.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,12 +42,15 @@ { ValueConstraint::ValueConstraint(const std::string& value, bool isCaseSensitive) : - value_(value), isCaseSensitive_(isCaseSensitive) { - if (!isCaseSensitive) + if (isCaseSensitive) { - Toolbox::ToUpperCase(value_); + value_ = value; + } + else + { + value_ = Toolbox::ToUpperCaseWithAccents(value); } } @@ -65,9 +69,7 @@ } else { - std::string v; - Toolbox::ToUpperCase(v, value); - return value_ == v; + return value_ == Toolbox::ToUpperCaseWithAccents(value); } } }
--- a/OrthancServer/Search/ValueConstraint.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/ValueConstraint.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,5 +62,10 @@ const DicomTag& tag) const; virtual bool Match(const std::string& value) const; + + virtual std::string Format() const + { + return value_; + } }; }
--- a/OrthancServer/Search/WildcardConstraint.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/WildcardConstraint.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +42,24 @@ { boost::regex pattern_; std::string wildcard_; + bool isCaseSensitive_; + + PImpl(const std::string& wildcard, + bool isCaseSensitive) + { + isCaseSensitive_ = isCaseSensitive; + + if (isCaseSensitive) + { + wildcard_ = wildcard; + } + else + { + wildcard_ = Toolbox::ToUpperCaseWithAccents(wildcard); + } + + pattern_ = boost::regex(Toolbox::WildcardToRegularExpression(wildcard_)); + } }; @@ -52,25 +71,20 @@ WildcardConstraint::WildcardConstraint(const std::string& wildcard, bool isCaseSensitive) : - pimpl_(new PImpl) + pimpl_(new PImpl(wildcard, isCaseSensitive)) { - pimpl_->wildcard_ = wildcard; - - std::string re = Toolbox::WildcardToRegularExpression(wildcard); - - if (isCaseSensitive) - { - pimpl_->pattern_ = boost::regex(re); - } - else - { - pimpl_->pattern_ = boost::regex(re, boost::regex::icase /* case insensitive search */); - } } bool WildcardConstraint::Match(const std::string& value) const { - return boost::regex_match(value, pimpl_->pattern_); + if (pimpl_->isCaseSensitive_) + { + return boost::regex_match(value, pimpl_->pattern_); + } + else + { + return boost::regex_match(Toolbox::ToUpperCaseWithAccents(value), pimpl_->pattern_); + } } void WildcardConstraint::Setup(LookupIdentifierQuery& lookup, @@ -78,4 +92,9 @@ { lookup.AddConstraint(tag, IdentifierConstraintType_Wildcard, pimpl_->wildcard_); } + + std::string WildcardConstraint::Format() const + { + return pimpl_->wildcard_; + } }
--- a/OrthancServer/Search/WildcardConstraint.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/Search/WildcardConstraint.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,5 +60,7 @@ const DicomTag& tag) const; virtual bool Match(const std::string& value) const; + + virtual std::string Format() const; }; }
--- a/OrthancServer/ServerContext.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerContext.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,26 +34,22 @@ #include "PrecompiledHeadersServer.h" #include "ServerContext.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/FileStorage/StorageAccessor.h" #include "../Core/HttpServer/FilesystemHttpSender.h" #include "../Core/HttpServer/HttpStreamTranscoder.h" #include "../Core/Logging.h" -#include "FromDcmtkBridge.h" +#include "../Plugins/Engine/OrthancPlugins.h" +#include "OrthancInitialization.h" +#include "OrthancRestApi/OrthancRestApi.h" +#include "Search/LookupResource.h" +#include "ServerJobs/OrthancJobUnserializer.h" #include "ServerToolbox.h" -#include "OrthancInitialization.h" #include <EmbeddedResources.h> #include <dcmtk/dcmdata/dcfilefo.h> -#include "Scheduler/CallSystemCommand.h" -#include "Scheduler/DeleteInstanceCommand.h" -#include "Scheduler/ModifyInstanceCommand.h" -#include "Scheduler/StoreScuCommand.h" -#include "Scheduler/StorePeerCommand.h" -#include "OrthancRestApi/OrthancRestApi.h" -#include "../Plugins/Engine/OrthancPlugins.h" - #define ENABLE_DICOM_CACHE 1 @@ -69,11 +66,12 @@ namespace Orthanc { - void ServerContext::ChangeThread(ServerContext* that) + void ServerContext::ChangeThread(ServerContext* that, + unsigned int sleepDelay) { while (!that->done_) { - std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(100)); + std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay)); if (obj.get() != NULL) { @@ -85,12 +83,24 @@ { try { - it->GetListener().SignalChange(change); + try + { + it->GetListener().SignalChange(change); + } + catch (std::bad_alloc&) + { + LOG(ERROR) << "Not enough memory while signaling a change"; + } + catch (...) + { + throw OrthancException(ErrorCode_InternalError); + } } catch (OrthancException& e) { LOG(ERROR) << "Error in the " << it->GetDescription() - << " callback while signaling a change: " << e.What(); + << " callback while signaling a change: " << e.What() + << " (code " << e.GetErrorCode() << ")"; } } } @@ -98,29 +108,139 @@ } + void ServerContext::SaveJobsThread(ServerContext* that, + unsigned int sleepDelay) + { + static const boost::posix_time::time_duration PERIODICITY = + boost::posix_time::seconds(10); + + boost::posix_time::ptime next = + boost::posix_time::microsec_clock::universal_time() + PERIODICITY; + + while (!that->done_) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDelay)); + + if (that->haveJobsChanged_ || + boost::posix_time::microsec_clock::universal_time() >= next) + { + that->haveJobsChanged_ = false; + that->SaveJobsEngine(); + next = boost::posix_time::microsec_clock::universal_time() + PERIODICITY; + } + } + } + + + void ServerContext::SignalJobSubmitted(const std::string& jobId) + { + haveJobsChanged_ = true; + mainLua_.SignalJobSubmitted(jobId); + } + + + void ServerContext::SignalJobSuccess(const std::string& jobId) + { + haveJobsChanged_ = true; + mainLua_.SignalJobSuccess(jobId); + } + + + void ServerContext::SignalJobFailure(const std::string& jobId) + { + haveJobsChanged_ = true; + mainLua_.SignalJobFailure(jobId); + } + + + void ServerContext::SetupJobsEngine(bool unitTesting, + bool loadJobsFromDatabase) + { + if (loadJobsFromDatabase) + { + std::string serialized; + if (index_.LookupGlobalProperty(serialized, GlobalProperty_JobsRegistry)) + { + LOG(WARNING) << "Reloading the jobs from the last execution of Orthanc"; + OrthancJobUnserializer unserializer(*this); + + try + { + jobsEngine_.LoadRegistryFromString(unserializer, serialized); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Cannot unserialize the jobs engine: " << e.What(); + throw; + } + } + else + { + LOG(INFO) << "The last execution of Orthanc has archived no job"; + } + } + else + { + LOG(WARNING) << "Not reloading the jobs from the last execution of Orthanc"; + } + + //jobsEngine_.GetRegistry().SetMaxCompleted // TODO + + jobsEngine_.GetRegistry().SetObserver(*this); + jobsEngine_.Start(); + isJobsEngineUnserialized_ = true; + + saveJobsThread_ = boost::thread(SaveJobsThread, this, (unitTesting ? 20 : 100)); + } + + + void ServerContext::SaveJobsEngine() + { + VLOG(1) << "Serializing the content of the jobs engine"; + + try + { + Json::Value value; + jobsEngine_.GetRegistry().Serialize(value); + + Json::FastWriter writer; + std::string serialized = writer.write(value); + + index_.SetGlobalProperty(GlobalProperty_JobsRegistry, serialized); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Cannot serialize the jobs engine: " << e.What(); + } + } + + ServerContext::ServerContext(IDatabaseWrapper& database, - IStorageArea& area) : - index_(*this, database), + IStorageArea& area, + bool unitTesting) : + index_(*this, database, (unitTesting ? 20 : 500)), area_(area), compressionEnabled_(false), storeMD5_(true), provider_(*this), dicomCache_(provider_, DICOM_CACHE_SIZE), - scheduler_(Configuration::GetGlobalIntegerParameter("LimitJobs", 10)), - lua_(*this), -#if ORTHANC_PLUGINS_ENABLED == 1 + mainLua_(*this), + filterLua_(*this), + luaListener_(*this), +#if ORTHANC_ENABLE_PLUGINS == 1 plugins_(NULL), #endif done_(false), - queryRetrieveArchive_(Configuration::GetGlobalIntegerParameter("QueryRetrieveSize", 10)), + haveJobsChanged_(false), + isJobsEngineUnserialized_(false), + queryRetrieveArchive_(Configuration::GetGlobalUnsignedIntegerParameter("QueryRetrieveSize", 10)), defaultLocalAet_(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")) { - uint64_t s = Configuration::GetGlobalIntegerParameter("DicomAssociationCloseDelay", 5); // In seconds - scu_.SetMillisecondsBeforeClose(s * 1000); // Milliseconds are expected here + jobsEngine_.SetWorkersCount(Configuration::GetGlobalUnsignedIntegerParameter("ConcurrentJobs", 2)); + jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); - listeners_.push_back(ServerListener(lua_, "Lua")); - - changeThread_ = boost::thread(ChangeThread, this); + listeners_.push_back(ServerListener(luaListener_, "Lua")); + changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100)); } @@ -151,10 +271,21 @@ changeThread_.join(); } - scu_.Finalize(); + if (saveJobsThread_.joinable()) + { + saveJobsThread_.join(); + } + + jobsEngine_.GetRegistry().ResetObserver(); + + if (isJobsEngineUnserialized_) + { + // Avoid losing jobs if the JobsRegistry cannot be unserialized + SaveJobsEngine(); + } // Do not change the order below! - scheduler_.Stop(); + jobsEngine_.Stop(); index_.Stop(); } } @@ -189,7 +320,7 @@ resultPublicId = hasher.HashInstance(); Json::Value simplifiedTags; - Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson()); + ServerToolbox::SimplifyTags(simplifiedTags, dicom.GetJson(), DicomToJsonFormat_Human); // Test if the instance must be filtered out bool accepted = true; @@ -210,7 +341,8 @@ catch (OrthancException& e) { LOG(ERROR) << "Error in the " << it->GetDescription() - << " callback while receiving an instance: " << e.What(); + << " callback while receiving an instance: " << e.What() + << " (code " << e.GetErrorCode() << ")"; throw; } } @@ -222,6 +354,13 @@ return StoreStatus_FilteredOut; } + { + // Remove the file from the DicomCache (useful if + // "OverwriteInstances" is set to "true") + boost::mutex::scoped_lock lock(dicomCacheMutex_); + dicomCache_.Invalidate(resultPublicId); + } + // TODO Should we use "gzip" instead? CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); @@ -236,8 +375,7 @@ typedef std::map<MetadataType, std::string> InstanceMetadata; InstanceMetadata instanceMetadata; - StoreStatus status = index_.Store(instanceMetadata, dicom.GetSummary(), attachments, - dicom.GetRemoteAet(), dicom.GetMetadata()); + StoreStatus status = index_.Store(instanceMetadata, dicom, attachments); // Only keep the metadata for the "instance" level dicom.GetMetadata().clear(); @@ -288,7 +426,8 @@ catch (OrthancException& e) { LOG(ERROR) << "Error in the " << it->GetDescription() - << " callback while receiving an instance: " << e.What(); + << " callback while receiving an instance: " << e.What() + << " (code " << e.GetErrorCode() << ")"; } } } @@ -299,7 +438,7 @@ { if (e.GetErrorCode() == ErrorCode_InexistentTag) { - Toolbox::LogMissingRequiredTag(dicom.GetSummary()); + dicom.GetSummary().LogMissingTagsForStore(); } throw; @@ -318,7 +457,7 @@ } StorageAccessor accessor(area_); - accessor.AnswerFile(output, attachment); + accessor.AnswerFile(output, attachment, GetFileContentMime(content)); } @@ -368,35 +507,101 @@ } - void ServerContext::ReadJson(Json::Value& result, - const std::string& instancePublicId) + void ServerContext::ReadDicomAsJsonInternal(std::string& result, + const std::string& instancePublicId) { - std::string s; - ReadFile(s, instancePublicId, FileContentType_DicomAsJson); + FileInfo attachment; + if (index_.LookupAttachment(attachment, instancePublicId, FileContentType_DicomAsJson)) + { + ReadAttachment(result, attachment); + } + else + { + // The "DICOM as JSON" summary is not available from the Orthanc + // store (most probably deleted), reconstruct it from the DICOM file + std::string dicom; + ReadDicom(dicom, instancePublicId); - Json::Reader reader; - if (!reader.parse(s, result)) - { - throw OrthancException(ErrorCode_CorruptedFile); + LOG(INFO) << "Reconstructing the missing DICOM-as-JSON summary for instance: " + << instancePublicId; + + ParsedDicomFile parsed(dicom); + + Json::Value summary; + parsed.DatasetToJson(summary); + + result = summary.toStyledString(); + + if (!AddAttachment(instancePublicId, FileContentType_DicomAsJson, + result.c_str(), result.size())) + { + LOG(WARNING) << "Cannot associate the DICOM-as-JSON summary to instance: " << instancePublicId; + throw OrthancException(ErrorCode_InternalError); + } } } - void ServerContext::ReadFile(std::string& result, - const std::string& instancePublicId, - FileContentType content, - bool uncompressIfNeeded) + void ServerContext::ReadDicomAsJson(std::string& result, + const std::string& instancePublicId, + const std::set<DicomTag>& ignoreTagLength) + { + if (ignoreTagLength.empty()) + { + ReadDicomAsJsonInternal(result, instancePublicId); + } + else + { + Json::Value tmp; + ReadDicomAsJson(tmp, instancePublicId, ignoreTagLength); + result = tmp.toStyledString(); + } + } + + + void ServerContext::ReadDicomAsJson(Json::Value& result, + const std::string& instancePublicId, + const std::set<DicomTag>& ignoreTagLength) + { + if (ignoreTagLength.empty()) + { + std::string tmp; + ReadDicomAsJsonInternal(tmp, instancePublicId); + + Json::Reader reader; + if (!reader.parse(tmp, result)) + { + throw OrthancException(ErrorCode_CorruptedFile); + } + } + else + { + // The "DicomAsJson" attachment might have stored some tags as + // "too long". We are forced to re-parse the DICOM file. + std::string dicom; + ReadDicom(dicom, instancePublicId); + + ParsedDicomFile parsed(dicom); + parsed.DatasetToJson(result, ignoreTagLength); + } + } + + + void ServerContext::ReadAttachment(std::string& result, + const std::string& instancePublicId, + FileContentType content, + bool uncompressIfNeeded) { FileInfo attachment; if (!index_.LookupAttachment(attachment, instancePublicId, content)) { + LOG(WARNING) << "Unable to read attachment " << EnumerationToString(content) << " of instance " << instancePublicId; throw OrthancException(ErrorCode_InternalError); } if (uncompressIfNeeded) { - StorageAccessor accessor(area_); - accessor.Read(result, attachment); + ReadAttachment(result, attachment); } else { @@ -407,10 +612,19 @@ } + void ServerContext::ReadAttachment(std::string& result, + const FileInfo& attachment) + { + // This will decompress the attachment + StorageAccessor accessor(area_); + accessor.Read(result, attachment); + } + + IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId) { std::string content; - context_.ReadFile(content, instancePublicId, FileContentType_Dicom); + context_.ReadDicom(content, instancePublicId); return new ParsedDicomFile(content); } @@ -472,6 +686,13 @@ const std::string& uuid, ResourceType expectedType) { + if (expectedType == ResourceType_Instance) + { + // remove the file from the DicomCache + boost::mutex::scoped_lock lock(dicomCacheMutex_); + dicomCache_.Invalidate(uuid); + } + return index_.DeleteResource(target, uuid, expectedType); } @@ -482,7 +703,7 @@ } -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 void ServerContext::SetPlugins(OrthancPlugins& plugins) { boost::recursive_mutex::scoped_lock lock(listenersMutex_); @@ -491,7 +712,7 @@ // TODO REFACTOR THIS listeners_.clear(); - listeners_.push_back(ServerListener(lua_, "Lua")); + listeners_.push_back(ServerListener(luaListener_, "Lua")); listeners_.push_back(ServerListener(plugins, "plugin")); } @@ -504,7 +725,7 @@ // TODO REFACTOR THIS listeners_.clear(); - listeners_.push_back(ServerListener(lua_, "Lua")); + listeners_.push_back(ServerListener(luaListener_, "Lua")); } @@ -537,7 +758,7 @@ bool ServerContext::HasPlugins() const { -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 return (plugins_ != NULL); #else return false; @@ -545,28 +766,39 @@ } - bool ServerContext::Apply(std::list<std::string>& result, + void ServerContext::Apply(bool& isComplete, + std::list<std::string>& result, const ::Orthanc::LookupResource& lookup, - size_t maxResults) + size_t since, + size_t limit) { result.clear(); + isComplete = true; std::vector<std::string> resources, instances; GetIndex().FindCandidates(resources, instances, lookup); assert(resources.size() == instances.size()); + size_t skipped = 0; for (size_t i = 0; i < instances.size(); i++) { + // TODO - Don't read the full JSON from the disk if only "main + // DICOM tags" are to be returned Json::Value dicom; - ReadJson(dicom, instances[i]); + ReadDicomAsJson(dicom, instances[i]); if (lookup.IsMatch(dicom)) { - if (maxResults != 0 && - result.size() >= maxResults) + if (skipped < since) { - return false; // too many results + skipped++; + } + else if (limit != 0 && + result.size() >= limit) + { + isComplete = false; + return; // too many results } else { @@ -574,8 +806,21 @@ } } } - - return true; // finished } + + void ServerContext::AddChildInstances(SetOfInstancesJob& job, + const std::string& publicId) + { + std::list<std::string> instances; + GetIndex().GetChildInstances(instances, publicId); + + job.Reserve(job.GetInstancesCount() + instances.size()); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + job.AddInstance(*it); + } + } }
--- a/OrthancServer/ServerContext.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerContext.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,22 +33,21 @@ #pragma once -#include "../Core/MultiThreading/SharedMessageQueue.h" +#include "DicomInstanceToStore.h" +#include "IServerListener.h" +#include "LuaScripting.h" +#include "OrthancHttpHandler.h" +#include "ServerIndex.h" + #include "../Core/Cache/MemoryCache.h" #include "../Core/Cache/SharedArchive.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "../Core/FileStorage/IStorageArea.h" -#include "../Core/Lua/LuaContext.h" +#include "../Core/JobsEngine/JobsEngine.h" +#include "../Core/JobsEngine/SetOfInstancesJob.h" +#include "../Core/MultiThreading/SharedMessageQueue.h" #include "../Core/RestApi/RestApiOutput.h" #include "../Plugins/Engine/OrthancPlugins.h" -#include "DicomInstanceToStore.h" -#include "DicomProtocol/ReusableDicomUserConnection.h" -#include "IServerListener.h" -#include "LuaScripting.h" -#include "ParsedDicomFile.h" -#include "Scheduler/ServerScheduler.h" -#include "ServerIndex.h" -#include "OrthancHttpHandler.h" -#include "Search/LookupResource.h" #include <boost/filesystem.hpp> #include <boost/thread.hpp> @@ -60,9 +60,39 @@ * filesystem (including compression), as well as the index of the * DICOM store. It implements the required locking mechanisms. **/ - class ServerContext + class ServerContext : private JobsRegistry::IObserver { private: + class LuaServerListener : public IServerListener + { + private: + ServerContext& context_; + + public: + LuaServerListener(ServerContext& context) : + context_(context) + { + } + + virtual void SignalStoredInstance(const std::string& publicId, + DicomInstanceToStore& instance, + const Json::Value& simplifiedTags) + { + context_.mainLua_.SignalStoredInstance(publicId, instance, simplifiedTags); + } + + virtual void SignalChange(const ServerIndexChange& change) + { + context_.mainLua_.SignalChange(change); + } + + virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) + { + return context_.filterLua_.FilterIncomingInstance(instance, simplified); + } + }; + class DicomCacheProvider : public ICachePageProvider { private: @@ -104,8 +134,22 @@ typedef std::list<ServerListener> ServerListeners; - static void ChangeThread(ServerContext* that); + static void ChangeThread(ServerContext* that, + unsigned int sleepDelay); + + static void SaveJobsThread(ServerContext* that, + unsigned int sleepDelay); + void ReadDicomAsJsonInternal(std::string& result, + const std::string& instancePublicId); + + void SaveJobsEngine(); + + virtual void SignalJobSubmitted(const std::string& jobId); + + virtual void SignalJobSuccess(const std::string& jobId); + + virtual void SignalJobFailure(const std::string& jobId); ServerIndex index_; IStorageArea& area_; @@ -116,12 +160,13 @@ DicomCacheProvider provider_; boost::mutex dicomCacheMutex_; MemoryCache dicomCache_; - ReusableDicomUserConnection scu_; - ServerScheduler scheduler_; + JobsEngine jobsEngine_; - LuaScripting lua_; + LuaScripting mainLua_; + LuaScripting filterLua_; + LuaServerListener luaListener_; -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 OrthancPlugins* plugins_; #endif @@ -129,8 +174,11 @@ boost::recursive_mutex listenersMutex_; bool done_; + bool haveJobsChanged_; + bool isJobsEngineUnserialized_; SharedMessageQueue pendingChanges_; boost::thread changeThread_; + boost::thread saveJobsThread_; SharedArchive queryRetrieveArchive_; std::string defaultLocalAet_; @@ -157,10 +205,14 @@ }; ServerContext(IDatabaseWrapper& database, - IStorageArea& area); + IStorageArea& area, + bool unitTesting); ~ServerContext(); + void SetupJobsEngine(bool unitTesting, + bool loadJobsFromDatabase); + ServerIndex& GetIndex() { return index_; @@ -192,14 +244,42 @@ FileContentType attachmentType, CompressionType compression); - void ReadJson(Json::Value& result, - const std::string& instancePublicId); + void ReadDicomAsJson(std::string& result, + const std::string& instancePublicId, + const std::set<DicomTag>& ignoreTagLength); + + void ReadDicomAsJson(Json::Value& result, + const std::string& instancePublicId, + const std::set<DicomTag>& ignoreTagLength); + void ReadDicomAsJson(std::string& result, + const std::string& instancePublicId) + { + std::set<DicomTag> ignoreTagLength; + ReadDicomAsJson(result, instancePublicId, ignoreTagLength); + } + + void ReadDicomAsJson(Json::Value& result, + const std::string& instancePublicId) + { + std::set<DicomTag> ignoreTagLength; + ReadDicomAsJson(result, instancePublicId, ignoreTagLength); + } + + void ReadDicom(std::string& dicom, + const std::string& instancePublicId) + { + ReadAttachment(dicom, instancePublicId, FileContentType_Dicom, true); + } + // TODO CACHING MECHANISM AT THIS POINT - void ReadFile(std::string& result, - const std::string& instancePublicId, - FileContentType content, - bool uncompressIfNeeded = true); + void ReadAttachment(std::string& result, + const std::string& instancePublicId, + FileContentType content, + bool uncompressIfNeeded); + + void ReadAttachment(std::string& result, + const FileInfo& attachment); void SetStoreMD5ForAttachments(bool storeMD5); @@ -208,14 +288,9 @@ return storeMD5_; } - ReusableDicomUserConnection& GetReusableDicomUserConnection() + JobsEngine& GetJobsEngine() { - return scu_; - } - - ServerScheduler& GetScheduler() - { - return scheduler_; + return jobsEngine_; } bool DeleteResource(Json::Value& target, @@ -234,9 +309,9 @@ return defaultLocalAet_; } - LuaScripting& GetLua() + LuaScripting& GetLuaScripting() { - return lua_; + return mainLua_; } OrthancHttpHandler& GetHttpHandler() @@ -246,16 +321,18 @@ void Stop(); - bool Apply(std::list<std::string>& result, + void Apply(bool& isComplete, + std::list<std::string>& result, const ::Orthanc::LookupResource& lookup, - size_t maxResults); + size_t since, + size_t limit); /** * Management of the plugins **/ -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 void SetPlugins(OrthancPlugins& plugins); void ResetPlugins(); @@ -266,5 +343,8 @@ #endif bool HasPlugins() const; + + void AddChildInstances(SetOfInstancesJob& job, + const std::string& publicId); }; }
--- a/OrthancServer/ServerEnumerations.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerEnumerations.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +36,19 @@ #include "../Core/OrthancException.h" #include "../Core/EnumerationDictionary.h" +#include "../Core/Logging.h" #include "../Core/Toolbox.h" #include <boost/thread.hpp> namespace Orthanc { + typedef std::map<FileContentType, std::string> MimeTypes; + static boost::mutex enumerationsMutex_; static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_; static Toolbox::EnumerationDictionary<FileContentType> dictContentType_; + static MimeTypes mimeTypes_; void InitializeServerEnumerations() { @@ -59,6 +64,12 @@ dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom"); dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate"); + dictMetadataType_.Add(MetadataType_Instance_Origin, "Origin"); + dictMetadataType_.Add(MetadataType_Instance_TransferSyntax, "TransferSyntax"); + dictMetadataType_.Add(MetadataType_Instance_SopClassUid, "SopClassUid"); + dictMetadataType_.Add(MetadataType_Instance_RemoteIp, "RemoteIP"); + dictMetadataType_.Add(MetadataType_Instance_CalledAet, "CalledAET"); + dictMetadataType_.Add(MetadataType_Instance_HttpUsername, "HttpUsername"); dictContentType_.Add(FileContentType_Dicom, "dicom"); dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json"); @@ -69,13 +80,29 @@ { boost::mutex::scoped_lock lock(enumerationsMutex_); - if (metadata < static_cast<int>(MetadataType_StartUser) || - metadata > static_cast<int>(MetadataType_EndUser)) + MetadataType type = static_cast<MetadataType>(metadata); + + if (metadata < 0 || + !IsUserMetadata(type)) { + LOG(ERROR) << "A user content type must have index between " + << static_cast<int>(MetadataType_StartUser) << " and " + << static_cast<int>(MetadataType_EndUser) << ", but \"" + << name << "\" has index " << metadata; + throw OrthancException(ErrorCode_ParameterOutOfRange); } - dictMetadataType_.Add(static_cast<MetadataType>(metadata), name); + if (dictMetadataType_.Contains(type)) + { + LOG(ERROR) << "Cannot associate user content type \"" + << name << "\" with index " << metadata + << ", as this index is already used"; + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + dictMetadataType_.Add(type, name); } std::string EnumerationToString(MetadataType type) @@ -93,17 +120,35 @@ } void RegisterUserContentType(int contentType, - const std::string& name) + const std::string& name, + const std::string& mime) { boost::mutex::scoped_lock lock(enumerationsMutex_); - if (contentType < static_cast<int>(FileContentType_StartUser) || - contentType > static_cast<int>(FileContentType_EndUser)) + FileContentType type = static_cast<FileContentType>(contentType); + + if (contentType < 0 || + !IsUserContentType(type)) { + LOG(ERROR) << "A user content type must have index between " + << static_cast<int>(FileContentType_StartUser) << " and " + << static_cast<int>(FileContentType_EndUser) << ", but \"" + << name << "\" has index " << contentType; + throw OrthancException(ErrorCode_ParameterOutOfRange); } - dictContentType_.Add(static_cast<FileContentType>(contentType), name); + if (dictContentType_.Contains(type)) + { + LOG(ERROR) << "Cannot associate user content type \"" + << name << "\" with index " << contentType + << ", as this index is already used"; + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + dictContentType_.Add(type, name); + mimeTypes_[type] = mime; } std::string EnumerationToString(FileContentType type) @@ -114,6 +159,33 @@ return dictContentType_.Translate(type); } + std::string GetFileContentMime(FileContentType type) + { + if (type >= FileContentType_StartUser && + type <= FileContentType_EndUser) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + + MimeTypes::const_iterator it = mimeTypes_.find(type); + if (it != mimeTypes_.end()) + { + return it->second; + } + } + + switch (type) + { + case FileContentType_Dicom: + return "application/dicom"; + + case FileContentType_DicomAsJson: + return "application/json"; + + default: + return "application/octet-stream"; + } + } + FileContentType StringToContentType(const std::string& str) { boost::mutex::scoped_lock lock(enumerationsMutex_); @@ -237,131 +309,21 @@ case ChangeType_NewChildInstance: return "NewChildInstance"; - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(ModalityManufacturer manufacturer) - { - switch (manufacturer) - { - case ModalityManufacturer_Generic: - return "Generic"; + case ChangeType_UpdatedAttachment: + return "UpdatedAttachment"; - case ModalityManufacturer_StoreScp: - return "StoreScp"; - - case ModalityManufacturer_ClearCanvas: - return "ClearCanvas"; - - case ModalityManufacturer_MedInria: - return "MedInria"; + case ChangeType_UpdatedMetadata: + return "UpdatedMetadata"; - case ModalityManufacturer_Dcm4Chee: - return "Dcm4Chee"; - - case ModalityManufacturer_SyngoVia: - return "SyngoVia"; - default: throw OrthancException(ErrorCode_ParameterOutOfRange); } } - - const char* EnumerationToString(DicomRequestType type) - { - switch (type) - { - case DicomRequestType_Echo: - return "Echo"; - break; - - case DicomRequestType_Find: - return "Find"; - break; - - case DicomRequestType_Get: - return "Get"; - break; - - case DicomRequestType_Move: - return "Move"; - break; - - case DicomRequestType_Store: - return "Store"; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - - ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer) + + bool IsUserMetadata(MetadataType metadata) { - if (manufacturer == "Generic") - { - return ModalityManufacturer_Generic; - } - else if (manufacturer == "ClearCanvas") - { - return ModalityManufacturer_ClearCanvas; - } - else if (manufacturer == "StoreScp") - { - return ModalityManufacturer_StoreScp; - } - else if (manufacturer == "MedInria") - { - return ModalityManufacturer_MedInria; - } - else if (manufacturer == "Dcm4Chee") - { - return ModalityManufacturer_Dcm4Chee; - } - else if (manufacturer == "SyngoVia") - { - return ModalityManufacturer_SyngoVia; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(TransferSyntax syntax) - { - switch (syntax) - { - case TransferSyntax_Deflated: - return "Deflated"; - - case TransferSyntax_Jpeg: - return "JPEG"; - - case TransferSyntax_Jpeg2000: - return "JPEG2000"; - - case TransferSyntax_JpegLossless: - return "JPEG Lossless"; - - case TransferSyntax_Jpip: - return "JPIP"; - - case TransferSyntax_Mpeg2: - return "MPEG2"; - - case TransferSyntax_Rle: - return "RLE"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } + return (metadata >= MetadataType_StartUser && + metadata <= MetadataType_EndUser); } }
--- a/OrthancServer/ServerEnumerations.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerEnumerations.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,76 +56,6 @@ StoreStatus_FilteredOut // Removed by NewInstanceFilter }; - enum ModalityManufacturer - { - ModalityManufacturer_Generic, - ModalityManufacturer_StoreScp, - ModalityManufacturer_ClearCanvas, - ModalityManufacturer_MedInria, - ModalityManufacturer_Dcm4Chee, - ModalityManufacturer_SyngoVia - }; - - enum DicomRequestType - { - DicomRequestType_Echo, - DicomRequestType_Find, - DicomRequestType_Get, - DicomRequestType_Move, - DicomRequestType_Store - }; - - enum DicomReplaceMode - { - DicomReplaceMode_InsertIfAbsent, - DicomReplaceMode_ThrowIfAbsent, - DicomReplaceMode_IgnoreIfAbsent - }; - - enum TransferSyntax - { - TransferSyntax_Deflated, - TransferSyntax_Jpeg, - TransferSyntax_Jpeg2000, - TransferSyntax_JpegLossless, - TransferSyntax_Jpip, - TransferSyntax_Mpeg2, - TransferSyntax_Rle - }; - - enum ValueRepresentation - { - ValueRepresentation_Other, - ValueRepresentation_PatientName, - ValueRepresentation_Date, - ValueRepresentation_DateTime, - ValueRepresentation_Time - }; - - enum DicomToJsonFormat - { - DicomToJsonFormat_Full, - DicomToJsonFormat_Short, - DicomToJsonFormat_Simple - }; - - enum DicomToJsonFlags - { - DicomToJsonFlags_IncludeBinary = (1 << 0), - DicomToJsonFlags_IncludePrivateTags = (1 << 1), - DicomToJsonFlags_IncludeUnknownTags = (1 << 2), - DicomToJsonFlags_IncludePixelData = (1 << 3), - DicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), - DicomToJsonFlags_ConvertBinaryToNull = (1 << 5), - - // Some predefined combinations - DicomToJsonFlags_None = 0, - DicomToJsonFlags_Default = (DicomToJsonFlags_IncludePrivateTags | - DicomToJsonFlags_IncludeUnknownTags | - DicomToJsonFlags_IncludePixelData | - DicomToJsonFlags_ConvertBinaryToNull) - }; - enum IdentifierConstraintType { IdentifierConstraintType_Equal, @@ -144,7 +75,23 @@ { GlobalProperty_DatabaseSchemaVersion = 1, // Unused in the Orthanc core as of Orthanc 0.9.5 GlobalProperty_FlushSleep = 2, - GlobalProperty_AnonymizationSequence = 3 + GlobalProperty_AnonymizationSequence = 3, + GlobalProperty_JobsRegistry = 5, + GlobalProperty_TotalCompressedSize = 6, // Reserved for Orthanc > 1.4.1 + GlobalProperty_TotalUncompressedSize = 7, // Reserved for Orthanc > 1.4.1 + + // Reserved values for internal use by the database plugins + GlobalProperty_DatabasePatchLevel = 4, + GlobalProperty_DatabaseInternal0 = 10, + GlobalProperty_DatabaseInternal1 = 11, + GlobalProperty_DatabaseInternal2 = 12, + GlobalProperty_DatabaseInternal3 = 13, + GlobalProperty_DatabaseInternal4 = 14, + GlobalProperty_DatabaseInternal5 = 15, + GlobalProperty_DatabaseInternal6 = 16, + GlobalProperty_DatabaseInternal7 = 17, + GlobalProperty_DatabaseInternal8 = 18, + GlobalProperty_DatabaseInternal9 = 19 }; enum MetadataType @@ -156,6 +103,12 @@ MetadataType_ModifiedFrom = 5, MetadataType_AnonymizedFrom = 6, MetadataType_LastUpdate = 7, + MetadataType_Instance_Origin = 8, // New in Orthanc 0.9.5 + MetadataType_Instance_TransferSyntax = 9, // New in Orthanc 1.2.0 + MetadataType_Instance_SopClassUid = 10, // New in Orthanc 1.2.0 + MetadataType_Instance_RemoteIp = 11, // New in Orthanc 1.4.0 + MetadataType_Instance_CalledAet = 12, // New in Orthanc 1.4.0 + MetadataType_Instance_HttpUsername = 13, // New in Orthanc 1.4.0 // Make sure that the value "65535" can be stored into this enumeration MetadataType_StartUser = 1024, @@ -178,6 +131,8 @@ ChangeType_StablePatient = 12, ChangeType_StableStudy = 13, ChangeType_StableSeries = 14, + ChangeType_UpdatedAttachment = 15, + ChangeType_UpdatedMetadata = 16, ChangeType_INTERNAL_LastLogged = 4095, @@ -198,12 +153,15 @@ std::string EnumerationToString(MetadataType type); void RegisterUserContentType(int contentType, - const std::string& name); + const std::string& name, + const std::string& mime); FileContentType StringToContentType(const std::string& str); std::string EnumerationToString(FileContentType type); + std::string GetFileContentMime(FileContentType type); + std::string GetBasePath(ResourceType type, const std::string& publicId); @@ -213,11 +171,5 @@ const char* EnumerationToString(ChangeType type); - const char* EnumerationToString(ModalityManufacturer manufacturer); - - const char* EnumerationToString(DicomRequestType type); - - const char* EnumerationToString(TransferSyntax syntax); - - ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); + bool IsUserMetadata(MetadataType type); }
--- a/OrthancServer/ServerIndex.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerIndex.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,17 +41,17 @@ #include "ServerIndexChange.h" #include "EmbeddedResources.h" #include "OrthancInitialization.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "ServerToolbox.h" #include "../Core/Toolbox.h" #include "../Core/Logging.h" -#include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomArray.h" -#include "Search/LookupIdentifierQuery.h" + +#include "../Core/DicomParsing/FromDcmtkBridge.h" +#include "ServerContext.h" +#include "DicomInstanceToStore.h" #include "Search/LookupResource.h" -#include "FromDcmtkBridge.h" -#include "ServerContext.h" - #include <boost/lexical_cast.hpp> #include <stdio.h> @@ -341,7 +342,8 @@ } - void ServerIndex::FlushThread(ServerIndex* that) + void ServerIndex::FlushThread(ServerIndex* that, + unsigned int threadSleep) { // By default, wait for 10 seconds before flushing unsigned int sleep = 10; @@ -367,13 +369,15 @@ while (!that->done_) { - boost::this_thread::sleep(boost::posix_time::seconds(1)); + boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleep)); count++; if (count < sleep) { continue; } + Logging::Flush(); + boost::mutex::scoped_lock lock(that->mutex_); that->db_.FlushToDisk(); count = 0; @@ -535,11 +539,13 @@ ServerIndex::ServerIndex(ServerContext& context, - IDatabaseWrapper& db) : + IDatabaseWrapper& db, + unsigned int threadSleep) : done_(false), db_(db), maximumStorageSize_(0), - maximumPatients_(0) + maximumPatients_(0), + overwrite_(false) { listener_.reset(new Listener(context)); db_.SetListener(*listener_); @@ -552,10 +558,11 @@ if (db.HasFlushToDisk()) { - flushThread_ = boost::thread(FlushThread, this); + flushThread_ = boost::thread(FlushThread, this, threadSleep); } - unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this); + unstableResourcesMonitorThread_ = boost::thread + (UnstableResourcesMonitorThread, this, threadSleep); } @@ -592,31 +599,54 @@ + void ServerIndex::SetInstanceMetadata(std::map<MetadataType, std::string>& instanceMetadata, + int64_t instance, + MetadataType metadata, + const std::string& value) + { + db_.SetMetadata(instance, metadata, value); + instanceMetadata[metadata] = value; + } + + + StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata, - const DicomMap& dicomSummary, - const Attachments& attachments, - const std::string& remoteAet, - const MetadataMap& metadata) + DicomInstanceToStore& instanceToStore, + const Attachments& attachments) { boost::mutex::scoped_lock lock(mutex_); + const DicomMap& dicomSummary = instanceToStore.GetSummary(); + const ServerIndex::MetadataMap& metadata = instanceToStore.GetMetadata(); + instanceMetadata.clear(); - DicomInstanceHasher hasher(dicomSummary); + DicomInstanceHasher hasher(instanceToStore.GetSummary()); try { Transaction t(*this); - // Do nothing if the instance already exists + // Check whether this instance is already stored { ResourceType type; int64_t tmp; if (db_.LookupResource(tmp, type, hasher.HashInstance())) { assert(type == ResourceType_Instance); - db_.GetAllMetadata(instanceMetadata, tmp); - return StoreStatus_AlreadyStored; + + if (overwrite_) + { + // Overwrite the old instance + LOG(INFO) << "Overwriting instance: " << hasher.HashInstance(); + db_.DeleteResource(tmp); + } + else + { + // Do nothing if the instance already exists + db_.GetAllMetadata(instanceMetadata, tmp); + return StoreStatus_AlreadyStored; + } } } @@ -632,7 +662,7 @@ // Create the instance int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance); - Toolbox::SetMainDicomTags(db_, instance, ResourceType_Instance, dicomSummary); + ServerToolbox::StoreMainDicomTags(db_, instance, ResourceType_Instance, dicomSummary); // Detect up to which level the patient/study/series/instance // hierarchy must be created @@ -684,21 +714,21 @@ if (isNewSeries) { series = CreateResource(hasher.HashSeries(), ResourceType_Series); - Toolbox::SetMainDicomTags(db_, series, ResourceType_Series, dicomSummary); + ServerToolbox::StoreMainDicomTags(db_, series, ResourceType_Series, dicomSummary); } // Create the study if needed if (isNewStudy) { study = CreateResource(hasher.HashStudy(), ResourceType_Study); - Toolbox::SetMainDicomTags(db_, study, ResourceType_Study, dicomSummary); + ServerToolbox::StoreMainDicomTags(db_, study, ResourceType_Study, dicomSummary); } // Create the patient if needed if (isNewPatient) { patient = CreateResource(hasher.HashPatient(), ResourceType_Patient); - Toolbox::SetMainDicomTags(db_, patient, ResourceType_Patient, dicomSummary); + ServerToolbox::StoreMainDicomTags(db_, patient, ResourceType_Patient, dicomSummary); } // Create the parent-to-child links @@ -746,8 +776,7 @@ break; case ResourceType_Instance: - db_.SetMetadata(instance, it->first.second, it->second); - instanceMetadata[it->first.second] = it->second; + SetInstanceMetadata(instanceMetadata, instance, it->first.second, it->second); break; default: @@ -756,28 +785,62 @@ } // Attach the auto-computed metadata for the patient/study/series levels - std::string now = Toolbox::GetNowIsoString(); + std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */); db_.SetMetadata(series, MetadataType_LastUpdate, now); db_.SetMetadata(study, MetadataType_LastUpdate, now); db_.SetMetadata(patient, MetadataType_LastUpdate, now); // Attach the auto-computed metadata for the instance level, // reflecting these additions into the input metadata map - db_.SetMetadata(instance, MetadataType_Instance_ReceptionDate, now); - instanceMetadata[MetadataType_Instance_ReceptionDate] = now; - - db_.SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet); - instanceMetadata[MetadataType_Instance_RemoteAet] = remoteAet; + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_ReceptionDate, now); + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_RemoteAet, + instanceToStore.GetOrigin().GetRemoteAetC()); + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_Origin, + EnumerationToString(instanceToStore.GetOrigin().GetRequestOrigin())); + + { + std::string s; + + if (instanceToStore.LookupTransferSyntax(s)) + { + // New in Orthanc 1.2.0 + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_TransferSyntax, s); + } + + if (instanceToStore.GetOrigin().LookupRemoteIp(s)) + { + // New in Orthanc 1.4.0 + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_RemoteIp, s); + } + + if (instanceToStore.GetOrigin().LookupCalledAet(s)) + { + // New in Orthanc 1.4.0 + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_CalledAet, s); + } + + if (instanceToStore.GetOrigin().LookupHttpUsername(s)) + { + // New in Orthanc 1.4.0 + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_HttpUsername, s); + } + } const DicomValue* value; + if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL && + !value->IsNull() && + !value->IsBinary()) + { + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_SopClassUid, value->GetContent()); + } + if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) { if (!value->IsNull() && !value->IsBinary()) { - db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->GetContent()); - instanceMetadata[MetadataType_Instance_IndexInSeries] = value->GetContent(); + SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_IndexInSeries, value->GetContent()); } } @@ -1155,7 +1218,12 @@ { boost::mutex::scoped_lock lock(mutex_); + + // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as + // "GetLastChange()" involves calls to "GetPublicId()" + Transaction transaction(*this); db_.GetChanges(changes, done, since, maxResults); + transaction.Commit(0); } FormatLog(target, changes, "Changes", done, since); @@ -1168,7 +1236,12 @@ { boost::mutex::scoped_lock lock(mutex_); + + // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as + // "GetLastChange()" involves calls to "GetPublicId()" + Transaction transaction(*this); db_.GetLastChange(changes); + transaction.Commit(0); } FormatLog(target, changes, "Changes", true, 0); @@ -1185,7 +1258,7 @@ ResourceType type; if (!db_.LookupResource(id, type, publicId)) { - throw OrthancException(ErrorCode_InternalError); + throw OrthancException(ErrorCode_InexistentItem); } std::string patientId; @@ -1206,22 +1279,34 @@ switch (currentType) { case ResourceType_Patient: - patientId = map.GetValue(DICOM_TAG_PATIENT_ID).GetContent(); + if (map.HasTag(DICOM_TAG_PATIENT_ID)) + { + patientId = map.GetValue(DICOM_TAG_PATIENT_ID).GetContent(); + } done = true; break; case ResourceType_Study: - studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(); + if (map.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) + { + studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(); + } currentType = ResourceType_Patient; break; case ResourceType_Series: - seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(); + if (map.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) + { + seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(); + } currentType = ResourceType_Study; break; case ResourceType_Instance: - sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent(); + if (map.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) + { + sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent(); + } currentType = ResourceType_Series; break; @@ -1242,7 +1327,7 @@ type, publicId, remoteModality, - Toolbox::GetNowIsoString(), + SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */), patientId, studyInstanceUid, seriesInstanceUid, @@ -1388,6 +1473,13 @@ StandaloneRecycling(); } + void ServerIndex::SetOverwriteInstances(bool overwrite) + { + boost::mutex::scoped_lock lock(mutex_); + overwrite_ = overwrite; + } + + void ServerIndex::StandaloneRecycling() { // WARNING: No mutex here, do not include this as a public method @@ -1525,6 +1617,7 @@ const std::string& value) { boost::mutex::scoped_lock lock(mutex_); + Transaction t(*this); ResourceType rtype; int64_t id; @@ -1534,6 +1627,13 @@ } db_.SetMetadata(id, type, value); + + if (IsUserMetadata(type)) + { + LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId); + } + + t.Commit(0); } @@ -1541,6 +1641,7 @@ MetadataType type) { boost::mutex::scoped_lock lock(mutex_); + Transaction t(*this); ResourceType rtype; int64_t id; @@ -1550,6 +1651,13 @@ } db_.DeleteMetadata(id, type); + + if (IsUserMetadata(type)) + { + LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId); + } + + t.Commit(0); } @@ -1822,9 +1930,10 @@ } - void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that) + void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that, + unsigned int threadSleep) { - int stableAge = Configuration::GetGlobalIntegerParameter("StableAge", 60); + int stableAge = Configuration::GetGlobalUnsignedIntegerParameter("StableAge", 60); if (stableAge <= 0) { stableAge = 60; @@ -1834,8 +1943,8 @@ while (!that->done_) { - // Check for stable resources each second - boost::this_thread::sleep(boost::posix_time::seconds(1)); + // Check for stable resources each few seconds + boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleep)); boost::mutex::scoped_lock lock(that->mutex_); @@ -1958,6 +2067,11 @@ db_.AddAttachment(resourceId, attachment); + if (IsUserContentType(attachment.GetContentType())) + { + LogChange(resourceId, ChangeType_UpdatedAttachment, resourceType, publicId); + } + t.Commit(attachment.GetCompressedSize()); return StoreStatus_Success; @@ -1979,6 +2093,11 @@ db_.DeleteAttachment(id, type); + if (IsUserContentType(type)) + { + LogChange(id, ChangeType_UpdatedAttachment, rtype, publicId); + } + t.Commit(0); } @@ -2026,13 +2145,20 @@ } + bool ServerIndex::LookupGlobalProperty(std::string& value, + GlobalProperty property) + { + boost::mutex::scoped_lock lock(mutex_); + return db_.LookupGlobalProperty(value, property); + } + + std::string ServerIndex::GetGlobalProperty(GlobalProperty property, const std::string& defaultValue) { - boost::mutex::scoped_lock lock(mutex_); - std::string value; - if (db_.LookupGlobalProperty(value, property)) + + if (LookupGlobalProperty(value, property)) { return value; } @@ -2134,7 +2260,7 @@ assert(db_.GetResourceType(*it) == lookup.GetLevel()); int64_t instance; - if (!Toolbox::FindOneChildInstance(instance, db_, *it, lookup.GetLevel())) + if (!ServerToolbox::FindOneChildInstance(instance, db_, *it, lookup.GetLevel())) { throw OrthancException(ErrorCode_InternalError); } @@ -2143,4 +2269,99 @@ instances[pos] = db_.GetPublicId(instance); } } + + + bool ServerIndex::LookupParent(std::string& target, + const std::string& publicId, + ResourceType parentType) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t id; + if (!db_.LookupResource(id, type, publicId)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + while (type != parentType) + { + int64_t parentId; + + if (type == ResourceType_Patient || // Cannot further go up in hierarchy + !db_.LookupParent(parentId, id)) + { + return false; + } + + id = parentId; + type = GetParentResourceType(type); + } + + target = db_.GetPublicId(id); + return true; + } + + + void ServerIndex::ReconstructInstance(ParsedDicomFile& dicom) + { + DicomMap summary; + dicom.ExtractDicomSummary(summary); + + DicomInstanceHasher hasher(summary); + + boost::mutex::scoped_lock lock(mutex_); + + try + { + Transaction t(*this); + + int64_t patient = -1, study = -1, series = -1, instance = -1; + + ResourceType dummy; + if (!db_.LookupResource(patient, dummy, hasher.HashPatient()) || + !db_.LookupResource(study, dummy, hasher.HashStudy()) || + !db_.LookupResource(series, dummy, hasher.HashSeries()) || + !db_.LookupResource(instance, dummy, hasher.HashInstance()) || + patient == -1 || + study == -1 || + series == -1 || + instance == -1) + { + throw OrthancException(ErrorCode_InternalError); + } + + db_.ClearMainDicomTags(patient); + db_.ClearMainDicomTags(study); + db_.ClearMainDicomTags(series); + db_.ClearMainDicomTags(instance); + + ServerToolbox::StoreMainDicomTags(db_, patient, ResourceType_Patient, summary); + ServerToolbox::StoreMainDicomTags(db_, study, ResourceType_Study, summary); + ServerToolbox::StoreMainDicomTags(db_, series, ResourceType_Series, summary); + ServerToolbox::StoreMainDicomTags(db_, instance, ResourceType_Instance, summary); + + { + std::string s; + if (dicom.LookupTransferSyntax(s)) + { + db_.SetMetadata(instance, MetadataType_Instance_TransferSyntax, s); + } + } + + const DicomValue* value; + if ((value = summary.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL && + !value->IsNull() && + !value->IsBinary()) + { + db_.SetMetadata(instance, MetadataType_Instance_SopClassUid, value->GetContent()); + } + + t.Commit(0); // No change in the DB size + } + catch (OrthancException& e) + { + LOG(ERROR) << "EXCEPTION [" << e.What() << "]"; + } + } }
--- a/OrthancServer/ServerIndex.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerIndex.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,11 +43,12 @@ #include "IDatabaseWrapper.h" - namespace Orthanc { class LookupResource; class ServerContext; + class DicomInstanceToStore; + class ParsedDicomFile; class ServerIndex : public boost::noncopyable { @@ -68,13 +70,16 @@ IDatabaseWrapper& db_; LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload> unstableResources_; - uint64_t currentStorageSize_; - uint64_t maximumStorageSize_; + uint64_t currentStorageSize_; + uint64_t maximumStorageSize_; unsigned int maximumPatients_; + bool overwrite_; - static void FlushThread(ServerIndex* that); + static void FlushThread(ServerIndex* that, + unsigned int threadSleep); - static void UnstableResourcesMonitorThread(ServerIndex* that); + static void UnstableResourcesMonitorThread(ServerIndex* that, + unsigned int threadSleep); void MainDicomTagsToJson(Json::Value& result, int64_t resourceId, @@ -115,9 +120,15 @@ int64_t CreateResource(const std::string& publicId, ResourceType type); + void SetInstanceMetadata(std::map<MetadataType, std::string>& instanceMetadata, + int64_t instance, + MetadataType metadata, + const std::string& value); + public: ServerIndex(ServerContext& context, - IDatabaseWrapper& database); + IDatabaseWrapper& database, + unsigned int threadSleep); ~ServerIndex(); @@ -139,11 +150,11 @@ // "count == 0" means no limit on the number of patients void SetMaximumPatientCount(unsigned int count); + void SetOverwriteInstances(bool overwrite); + StoreStatus Store(std::map<MetadataType, std::string>& instanceMetadata, - const DicomMap& dicomSummary, - const Attachments& attachments, - const std::string& remoteAet, - const MetadataMap& metadata); + DicomInstanceToStore& instance, + const Attachments& attachments); void ComputeStatistics(Json::Value& target); @@ -250,6 +261,9 @@ void SetGlobalProperty(GlobalProperty property, const std::string& value); + bool LookupGlobalProperty(std::string& value, + GlobalProperty property); + std::string GetGlobalProperty(GlobalProperty property, const std::string& defaultValue); @@ -266,5 +280,11 @@ void FindCandidates(std::vector<std::string>& resources, std::vector<std::string>& instances, const ::Orthanc::LookupResource& lookup); + + bool LookupParent(std::string& target, + const std::string& publicId, + ResourceType parentType); + + void ReconstructInstance(ParsedDicomFile& dicom); }; }
--- a/OrthancServer/ServerIndexChange.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerIndexChange.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +35,7 @@ #include "ServerEnumerations.h" #include "../Core/IDynamicObject.h" -#include "../Core/Toolbox.h" +#include "../Core/SystemToolbox.h" #include <string> #include <json/value.h> @@ -58,7 +59,7 @@ changeType_(changeType), resourceType_(resourceType), publicId_(publicId), - date_(Toolbox::GetNowIsoString()) + date_(SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */)) { }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ArchiveJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,904 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ArchiveJob.h" + +#include "../../Core/Compression/HierarchicalZipWriter.h" +#include "../../Core/DicomParsing/DicomDirWriter.h" +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" + +#include <stdio.h> + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + +static const uint64_t MEGA_BYTES = 1024 * 1024; +static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; +static const char* MEDIA_IMAGES_FOLDER = "IMAGES"; + +namespace Orthanc +{ + static bool IsZip64Required(uint64_t uncompressedSize, + unsigned int countInstances) + { + static const uint64_t SAFETY_MARGIN = 64 * MEGA_BYTES; // Should be large enough to hold DICOMDIR + static const unsigned int FILES_MARGIN = 10; + + /** + * Determine whether ZIP64 is required. Original ZIP format can + * store up to 2GB of data (some implementation supporting up to + * 4GB of data), and up to 65535 files. + * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 + **/ + + const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN || + countInstances >= 65535 - FILES_MARGIN); + + LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " + << (uncompressedSize / MEGA_BYTES) << "MB using the " + << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; + + return isZip64; + } + + + class ArchiveJob::ResourceIdentifiers : public boost::noncopyable + { + private: + ResourceType level_; + std::string patient_; + std::string study_; + std::string series_; + std::string instance_; + + static void GoToParent(ServerIndex& index, + std::string& current) + { + std::string tmp; + + if (index.LookupParent(tmp, current)) + { + current = tmp; + } + else + { + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + public: + ResourceIdentifiers(ServerIndex& index, + const std::string& publicId) + { + if (!index.LookupResourceType(level_, publicId)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + std::string current = publicId;; + switch (level_) // Do not add "break" below! + { + case ResourceType_Instance: + instance_ = current; + GoToParent(index, current); + + case ResourceType_Series: + series_ = current; + GoToParent(index, current); + + case ResourceType_Study: + study_ = current; + GoToParent(index, current); + + case ResourceType_Patient: + patient_ = current; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + ResourceType GetLevel() const + { + return level_; + } + + const std::string& GetIdentifier(ResourceType level) const + { + // Some sanity check to ensure enumerations are not altered + assert(ResourceType_Patient < ResourceType_Study); + assert(ResourceType_Study < ResourceType_Series); + assert(ResourceType_Series < ResourceType_Instance); + + if (level > level_) + { + throw OrthancException(ErrorCode_InternalError); + } + + switch (level) + { + case ResourceType_Patient: + return patient_; + + case ResourceType_Study: + return study_; + + case ResourceType_Series: + return series_; + + case ResourceType_Instance: + return instance_; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + }; + + + class ArchiveJob::IArchiveVisitor : public boost::noncopyable + { + public: + virtual ~IArchiveVisitor() + { + } + + virtual void Open(ResourceType level, + const std::string& publicId) = 0; + + virtual void Close() = 0; + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) = 0; + }; + + + class ArchiveJob::ArchiveIndex : public boost::noncopyable + { + private: + struct Instance + { + std::string id_; + FileInfo dicom_; + + Instance(const std::string& id, + const FileInfo& dicom) : + id_(id), dicom_(dicom) + { + } + }; + + // A "NULL" value for ArchiveIndex indicates a non-expanded node + typedef std::map<std::string, ArchiveIndex*> Resources; + + ResourceType level_; + Resources resources_; // Only at patient/study/series level + std::list<Instance> instances_; // Only at instance level + + + void AddResourceToExpand(ServerIndex& index, + const std::string& id) + { + if (level_ == ResourceType_Instance) + { + FileInfo tmp; + if (index.LookupAttachment(tmp, id, FileContentType_Dicom)) + { + instances_.push_back(Instance(id, tmp)); + } + } + else + { + resources_[id] = NULL; + } + } + + + public: + ArchiveIndex(ResourceType level) : + level_(level) + { + } + + ~ArchiveIndex() + { + for (Resources::iterator it = resources_.begin(); + it != resources_.end(); ++it) + { + delete it->second; + } + } + + + void Add(ServerIndex& index, + const ResourceIdentifiers& resource) + { + const std::string& id = resource.GetIdentifier(level_); + Resources::iterator previous = resources_.find(id); + + if (level_ == ResourceType_Instance) + { + AddResourceToExpand(index, id); + } + else if (resource.GetLevel() == level_) + { + // Mark this resource for further expansion + if (previous != resources_.end()) + { + delete previous->second; + } + + resources_[id] = NULL; + } + else if (previous == resources_.end()) + { + // This is the first time we meet this resource + std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); + child->Add(index, resource); + resources_[id] = child.release(); + } + else if (previous->second != NULL) + { + previous->second->Add(index, resource); + } + else + { + // Nothing to do: This item is marked for further expansion + } + } + + + void Expand(ServerIndex& index) + { + if (level_ == ResourceType_Instance) + { + // Expanding an instance node makes no sense + return; + } + + for (Resources::iterator it = resources_.begin(); + it != resources_.end(); ++it) + { + if (it->second == NULL) + { + // This is resource is marked for expansion + std::list<std::string> children; + index.GetChildren(children, it->first); + + std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); + + for (std::list<std::string>::const_iterator + it2 = children.begin(); it2 != children.end(); ++it2) + { + child->AddResourceToExpand(index, *it2); + } + + it->second = child.release(); + } + + assert(it->second != NULL); + it->second->Expand(index); + } + } + + + void Apply(IArchiveVisitor& visitor) const + { + if (level_ == ResourceType_Instance) + { + for (std::list<Instance>::const_iterator + it = instances_.begin(); it != instances_.end(); ++it) + { + visitor.AddInstance(it->id_, it->dicom_); + } + } + else + { + for (Resources::const_iterator it = resources_.begin(); + it != resources_.end(); ++it) + { + assert(it->second != NULL); // There must have been a call to "Expand()" + visitor.Open(level_, it->first); + it->second->Apply(visitor); + visitor.Close(); + } + } + } + }; + + + + class ArchiveJob::ZipCommands : public boost::noncopyable + { + private: + enum Type + { + Type_OpenDirectory, + Type_CloseDirectory, + Type_WriteInstance + }; + + class Command : public boost::noncopyable + { + private: + Type type_; + std::string filename_; + std::string instanceId_; + FileInfo info_; + + public: + explicit Command(Type type) : + type_(type) + { + assert(type_ == Type_CloseDirectory); + } + + Command(Type type, + const std::string& filename) : + type_(type), + filename_(filename) + { + assert(type_ == Type_OpenDirectory); + } + + Command(Type type, + const std::string& filename, + const std::string& instanceId, + const FileInfo& info) : + type_(type), + filename_(filename), + instanceId_(instanceId), + info_(info) + { + assert(type_ == Type_WriteInstance); + } + + void Apply(HierarchicalZipWriter& writer, + ServerContext& context, + DicomDirWriter* dicomDir, + const std::string& dicomDirFolder) const + { + switch (type_) + { + case Type_OpenDirectory: + writer.OpenDirectory(filename_.c_str()); + break; + + case Type_CloseDirectory: + writer.CloseDirectory(); + break; + + case Type_WriteInstance: + { + std::string content; + + try + { + context.ReadAttachment(content, info_); + } + catch (OrthancException& e) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instanceId_; + return; + } + + //boost::this_thread::sleep(boost::posix_time::milliseconds(300)); + + writer.OpenFile(filename_.c_str()); + writer.Write(content); + + if (dicomDir != NULL) + { + ParsedDicomFile parsed(content); + dicomDir->Add(dicomDirFolder, filename_, parsed); + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + }; + + std::deque<Command*> commands_; + uint64_t uncompressedSize_; + unsigned int instancesCount_; + + + void ApplyInternal(HierarchicalZipWriter& writer, + ServerContext& context, + size_t index, + DicomDirWriter* dicomDir, + const std::string& dicomDirFolder) const + { + if (index >= commands_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + commands_[index]->Apply(writer, context, dicomDir, dicomDirFolder); + } + + public: + ZipCommands() : + uncompressedSize_(0), + instancesCount_(0) + { + } + + ~ZipCommands() + { + for (std::deque<Command*>::iterator it = commands_.begin(); + it != commands_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + } + + size_t GetSize() const + { + return commands_.size(); + } + + unsigned int GetInstancesCount() const + { + return instancesCount_; + } + + uint64_t GetUncompressedSize() const + { + return uncompressedSize_; + } + + void Apply(HierarchicalZipWriter& writer, + ServerContext& context, + size_t index, + DicomDirWriter& dicomDir, + const std::string& dicomDirFolder) const + { + ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder); + } + + void Apply(HierarchicalZipWriter& writer, + ServerContext& context, + size_t index) const + { + ApplyInternal(writer, context, index, NULL, ""); + } + + void AddOpenDirectory(const std::string& filename) + { + commands_.push_back(new Command(Type_OpenDirectory, filename)); + } + + void AddCloseDirectory() + { + commands_.push_back(new Command(Type_CloseDirectory)); + } + + void AddWriteInstance(const std::string& filename, + const std::string& instanceId, + const FileInfo& info) + { + commands_.push_back(new Command(Type_WriteInstance, filename, instanceId, info)); + instancesCount_ ++; + uncompressedSize_ += info.GetUncompressedSize(); + } + + bool IsZip64() const + { + return IsZip64Required(GetUncompressedSize(), GetInstancesCount()); + } + }; + + + + class ArchiveJob::ArchiveIndexVisitor : public IArchiveVisitor + { + private: + ZipCommands& commands_; + ServerContext& context_; + char instanceFormat_[24]; + unsigned int counter_; + + static std::string GetTag(const DicomMap& tags, + const DicomTag& tag) + { + const DicomValue* v = tags.TestAndGetValue(tag); + if (v != NULL && + !v->IsBinary() && + !v->IsNull()) + { + return v->GetContent(); + } + else + { + return ""; + } + } + + public: + ArchiveIndexVisitor(ZipCommands& commands, + ServerContext& context) : + commands_(commands), + context_(context), + counter_(0) + { + if (commands.GetSize() != 0) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); + } + + virtual void Open(ResourceType level, + const std::string& publicId) + { + std::string path; + + DicomMap tags; + if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level)) + { + switch (level) + { + case ResourceType_Patient: + path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME); + break; + + case ResourceType_Study: + path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION); + break; + + case ResourceType_Series: + { + std::string modality = GetTag(tags, DICOM_TAG_MODALITY); + path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION); + + if (modality.size() == 0) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); + } + else if (modality.size() == 1) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", + toupper(modality[0])); + } + else if (modality.size() >= 2) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", + toupper(modality[0]), toupper(modality[1])); + } + + counter_ = 0; + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path)); + + if (path.empty()) + { + path = std::string("Unknown ") + EnumerationToString(level); + } + + commands_.AddOpenDirectory(path.c_str()); + } + + virtual void Close() + { + commands_.AddCloseDirectory(); + } + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) + { + char filename[24]; + snprintf(filename, sizeof(filename) - 1, instanceFormat_, counter_); + counter_ ++; + + commands_.AddWriteInstance(filename, instanceId, dicom); + } + }; + + + class ArchiveJob::MediaIndexVisitor : public IArchiveVisitor + { + private: + ZipCommands& commands_; + ServerContext& context_; + unsigned int counter_; + + public: + MediaIndexVisitor(ZipCommands& commands, + ServerContext& context) : + commands_(commands), + context_(context), + counter_(0) + { + } + + virtual void Open(ResourceType level, + const std::string& publicId) + { + } + + virtual void Close() + { + } + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) + { + // "DICOM restricts the filenames on DICOM media to 8 + // characters (some systems wrongly use 8.3, but this does not + // conform to the standard)." + std::string filename = "IM" + boost::lexical_cast<std::string>(counter_); + commands_.AddWriteInstance(filename, instanceId, dicom); + + counter_ ++; + } + }; + + + 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_; + + public: + ZipWriterIterator(TemporaryFile& target, + ServerContext& context, + ArchiveIndex& archive, + bool isMedia, + bool enableExtendedSopClass) : + target_(target), + context_(context), + isMedia_(isMedia) + { + if (isMedia) + { + MediaIndexVisitor visitor(commands_, context); + archive.Expand(context.GetIndex()); + + commands_.AddOpenDirectory(MEDIA_IMAGES_FOLDER); + archive.Apply(visitor); + commands_.AddCloseDirectory(); + + dicomDir_.reset(new DicomDirWriter); + dicomDir_->EnableExtendedSopClass(enableExtendedSopClass); + } + else + { + ArchiveIndexVisitor visitor(commands_, context); + archive.Expand(context.GetIndex()); + archive.Apply(visitor); + } + + zip_.reset(new HierarchicalZipWriter(target.GetPath().c_str())); + zip_->SetZip64(commands_.IsZip64()); + } + + size_t GetStepsCount() const + { + return commands_.GetSize() + 1; + } + + void RunStep(size_t index) + { + if (index > commands_.GetSize()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else if (index == commands_.GetSize()) + { + // Last step: Add the DICOMDIR + if (isMedia_) + { + assert(dicomDir_.get() != NULL); + std::string s; + dicomDir_->Encode(s); + + zip_->OpenFile("DICOMDIR"); + zip_->Write(s); + } + } + else + { + if (isMedia_) + { + assert(dicomDir_.get() != NULL); + commands_.Apply(*zip_, context_, index, *dicomDir_, MEDIA_IMAGES_FOLDER); + } + else + { + assert(dicomDir_.get() == NULL); + commands_.Apply(*zip_, context_, index); + } + } + } + + unsigned int GetInstancesCount() const + { + return commands_.GetInstancesCount(); + } + + uint64_t GetUncompressedSize() const + { + return commands_.GetUncompressedSize(); + } + }; + + + ArchiveJob::ArchiveJob(boost::shared_ptr<TemporaryFile>& target, + ServerContext& context, + bool isMedia, + bool enableExtendedSopClass) : + target_(target), + context_(context), + archive_(new ArchiveIndex(ResourceType_Patient)), // root + isMedia_(isMedia), + enableExtendedSopClass_(enableExtendedSopClass), + currentStep_(0), + instancesCount_(0), + uncompressedSize_(0) + { + if (target.get() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + + void ArchiveJob::AddResource(const std::string& publicId) + { + if (writer_.get() != NULL) // Already started + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + ResourceIdentifiers resource(context_.GetIndex(), publicId); + archive_->Add(context_.GetIndex(), resource); + } + + + void ArchiveJob::Reset() + { + LOG(ERROR) << "Cannot resubmit the creation of an archive"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + + void ArchiveJob::Start() + { + if (writer_.get() != NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + writer_.reset(new ZipWriterIterator(*target_, context_, *archive_, + isMedia_, enableExtendedSopClass_)); + + instancesCount_ = writer_->GetInstancesCount(); + uncompressedSize_ = writer_->GetUncompressedSize(); + } + + + JobStepResult ArchiveJob::Step() + { + assert(writer_.get() != NULL); + + if (target_.unique()) + { + LOG(WARNING) << "A client has disconnected while creating an archive"; + return JobStepResult::Failure(ErrorCode_NetworkProtocol); + } + + if (writer_->GetStepsCount() == 0) + { + writer_.reset(); // Flush all the results + return JobStepResult::Success(); + } + else + { + writer_->RunStep(currentStep_); + + currentStep_ ++; + + if (currentStep_ == writer_->GetStepsCount()) + { + writer_.reset(); // Flush all the results + return JobStepResult::Success(); + } + else + { + return JobStepResult::Continue(); + } + } + } + + + float ArchiveJob::GetProgress() + { + if (writer_.get() == NULL || + writer_->GetStepsCount() == 0) + { + return 1; + } + else + { + return (static_cast<float>(currentStep_) / + static_cast<float>(writer_->GetStepsCount() - 1)); + } + } + + + void ArchiveJob::GetJobType(std::string& target) + { + if (isMedia_) + { + target = "Media"; + } + else + { + target = "Archive"; + } + } + + + void ArchiveJob::GetPublicContent(Json::Value& value) + { + value["Description"] = description_; + value["InstancesCount"] = instancesCount_; + value["UncompressedSizeMB"] = + static_cast<unsigned int>(uncompressedSize_ / MEGA_BYTES); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ArchiveJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,104 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/IJob.h" +#include "../../Core/TemporaryFile.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class ArchiveJob : public IJob + { + private: + class ArchiveIndex; + class ArchiveIndexVisitor; + class IArchiveVisitor; + class MediaIndexVisitor; + class ResourceIdentifiers; + class ZipCommands; + class ZipWriterIterator; + + boost::shared_ptr<TemporaryFile> target_; + ServerContext& context_; + boost::shared_ptr<ArchiveIndex> archive_; + bool isMedia_; + bool enableExtendedSopClass_; + std::string description_; + + boost::shared_ptr<ZipWriterIterator> writer_; + size_t currentStep_; + unsigned int instancesCount_; + uint64_t uncompressedSize_; + + public: + ArchiveJob(boost::shared_ptr<TemporaryFile>& target, + ServerContext& context, + bool isMedia, + bool enableExtendedSopClass); + + void SetDescription(const std::string& description) + { + description_ = description; + } + + const std::string& GetDescription() const + { + return description_; + } + + void AddResource(const std::string& publicId); + + virtual void Reset(); + + virtual void Start(); + + virtual JobStepResult Step(); + + virtual void Stop(JobStopReason reason) + { + } + + virtual float GetProgress(); + + virtual void GetJobType(std::string& target); + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value) + { + return false; // Cannot serialize this kind of job + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,228 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomModalityStoreJob.h" + +#include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + void DicomModalityStoreJob::OpenConnection() + { + if (connection_.get() == NULL) + { + connection_.reset(new DicomUserConnection); + connection_->SetLocalApplicationEntityTitle(localAet_); + connection_->SetRemoteModality(remote_); + } + } + + + bool DicomModalityStoreJob::HandleInstance(const std::string& instance) + { + assert(IsStarted()); + OpenConnection(); + + LOG(INFO) << "Sending instance " << instance << " to modality \"" + << remote_.GetApplicationEntityTitle() << "\""; + + std::string dicom; + + try + { + context_.ReadDicom(dicom, instance); + } + catch (OrthancException& e) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + if (HasMoveOriginator()) + { + connection_->Store(dicom, moveOriginatorAet_, moveOriginatorId_); + } + else + { + connection_->Store(dicom); + } + + //boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + return true; + } + + + bool DicomModalityStoreJob::HandleTrailingStep() + { + throw OrthancException(ErrorCode_InternalError); + } + + + DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context) : + context_(context), + localAet_("ORTHANC"), + moveOriginatorId_(0) // By default, not a C-MOVE + { + } + + + void DicomModalityStoreJob::SetLocalAet(const std::string& aet) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + localAet_ = aet; + } + } + + + void DicomModalityStoreJob::SetRemoteModality(const RemoteModalityParameters& remote) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + remote_ = remote; + } + } + + + const std::string& DicomModalityStoreJob::GetMoveOriginatorAet() const + { + if (HasMoveOriginator()) + { + return moveOriginatorAet_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + uint16_t DicomModalityStoreJob::GetMoveOriginatorId() const + { + if (HasMoveOriginator()) + { + return moveOriginatorId_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomModalityStoreJob::SetMoveOriginator(const std::string& aet, + int id) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (id < 0 || + id >= 65536) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + moveOriginatorId_ = static_cast<uint16_t>(id); + moveOriginatorAet_ = aet; + } + } + + void DicomModalityStoreJob::Stop(JobStopReason reason) // For pausing jobs + { + connection_.reset(NULL); + } + + + void DicomModalityStoreJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + + value["LocalAet"] = localAet_; + value["RemoteAet"] = remote_.GetApplicationEntityTitle(); + + if (HasMoveOriginator()) + { + value["MoveOriginatorAET"] = GetMoveOriginatorAet(); + value["MoveOriginatorID"] = GetMoveOriginatorId(); + } + } + + + static const char* LOCAL_AET = "LocalAet"; + static const char* REMOTE = "Remote"; + static const char* MOVE_ORIGINATOR_AET = "MoveOriginatorAet"; + static const char* MOVE_ORIGINATOR_ID = "MoveOriginatorId"; + + + DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), + context_(context) + { + localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); + remote_ = RemoteModalityParameters(serialized[REMOTE]); + moveOriginatorAet_ = SerializationToolbox::ReadString(serialized, MOVE_ORIGINATOR_AET); + moveOriginatorId_ = static_cast<uint16_t> + (SerializationToolbox::ReadUnsignedInteger(serialized, MOVE_ORIGINATOR_ID)); + } + + + bool DicomModalityStoreJob::Serialize(Json::Value& target) + { + if (!SetOfInstancesJob::Serialize(target)) + { + return false; + } + else + { + target[LOCAL_AET] = localAet_; + remote_.Serialize(target[REMOTE], true /* force advanced format */); + target[MOVE_ORIGINATOR_AET] = moveOriginatorAet_; + target[MOVE_ORIGINATOR_ID] = moveOriginatorId_; + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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/JobsEngine/SetOfInstancesJob.h" +#include "../../Core/DicomNetworking/DicomUserConnection.h" + +#include "../ServerContext.h" + +namespace Orthanc +{ + class DicomModalityStoreJob : public SetOfInstancesJob + { + private: + ServerContext& context_; + std::string localAet_; + RemoteModalityParameters remote_; + std::string moveOriginatorAet_; + uint16_t moveOriginatorId_; + std::auto_ptr<DicomUserConnection> connection_; + + void OpenConnection(); + + protected: + virtual bool HandleInstance(const std::string& instance); + + virtual bool HandleTrailingStep(); + + public: + DicomModalityStoreJob(ServerContext& context); + + DicomModalityStoreJob(ServerContext& context, + const Json::Value& serialized); + + const std::string& GetLocalAet() const + { + return localAet_; + } + + void SetLocalAet(const std::string& aet); + + const RemoteModalityParameters& GetRemoteModality() const + { + return remote_; + } + + void SetRemoteModality(const RemoteModalityParameters& remote); + + bool HasMoveOriginator() const + { + return moveOriginatorId_ != 0; + } + + const std::string& GetMoveOriginatorAet() const; + + uint16_t GetMoveOriginatorId() const; + + void SetMoveOriginator(const std::string& aet, + int id); + + virtual void Stop(JobStopReason reason); + + virtual void GetJobType(std::string& target) + { + target = "DicomModalityStore"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,199 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomMoveScuJob.h" + +#include "../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + class DicomMoveScuJob::Command : public SetOfCommandsJob::ICommand + { + private: + DicomMoveScuJob& that_; + std::auto_ptr<DicomMap> findAnswer_; + + public: + Command(DicomMoveScuJob& that, + const DicomMap& findAnswer) : + that_(that), + findAnswer_(findAnswer.Clone()) + { + } + + virtual bool Execute() + { + that_.Retrieve(*findAnswer_); + return true; + } + + virtual void Serialize(Json::Value& target) const + { + findAnswer_->Serialize(target); + } + }; + + + class DicomMoveScuJob::Unserializer : + public SetOfCommandsJob::ICommandUnserializer + { + private: + DicomMoveScuJob& that_; + + public: + Unserializer(DicomMoveScuJob& that) : + that_(that) + { + } + + virtual ICommand* Unserialize(const Json::Value& source) const + { + DicomMap findAnswer; + findAnswer.Unserialize(source); + return new Command(that_, findAnswer); + } + }; + + + + void DicomMoveScuJob::Retrieve(const DicomMap& findAnswer) + { + if (connection_.get() == NULL) + { + connection_.reset(new DicomUserConnection(localAet_, remote_)); + connection_->Open(); + } + + connection_->Move(targetAet_, findAnswer); + } + + + void DicomMoveScuJob::AddFindAnswer(const DicomMap& answer) + { + AddCommand(new Command(*this, answer)); + } + + + void DicomMoveScuJob::AddFindAnswer(QueryRetrieveHandler& query, + size_t i) + { + DicomMap answer; + query.GetAnswer(answer, i); + AddFindAnswer(answer); + } + + + void DicomMoveScuJob::SetLocalAet(const std::string& aet) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + localAet_ = aet; + } + } + + + void DicomMoveScuJob::SetTargetAet(const std::string& aet) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + targetAet_ = aet; + } + } + + + void DicomMoveScuJob::SetRemoteModality(const RemoteModalityParameters& remote) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + remote_ = remote; + } + } + + + void DicomMoveScuJob::Stop(JobStopReason reason) + { + connection_.reset(); + } + + + void DicomMoveScuJob::GetPublicContent(Json::Value& value) + { + SetOfCommandsJob::GetPublicContent(value); + + value["LocalAet"] = localAet_; + value["RemoteAet"] = remote_.GetApplicationEntityTitle(); + } + + + static const char* LOCAL_AET = "LocalAet"; + static const char* TARGET_AET = "TargetAet"; + static const char* REMOTE = "Remote"; + + DicomMoveScuJob::DicomMoveScuJob(ServerContext& context, + const Json::Value& serialized) : + SetOfCommandsJob(new Unserializer(*this), serialized), + context_(context) + { + localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); + targetAet_ = SerializationToolbox::ReadString(serialized, TARGET_AET); + remote_ = RemoteModalityParameters(serialized[REMOTE]); + } + + + bool DicomMoveScuJob::Serialize(Json::Value& target) + { + if (!SetOfCommandsJob::Serialize(target)) + { + return false; + } + else + { + target[LOCAL_AET] = localAet_; + target[TARGET_AET] = targetAet_; + remote_.Serialize(target[REMOTE], true /* force advanced format */); + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,104 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/SetOfCommandsJob.h" +#include "../../Core/DicomNetworking/DicomUserConnection.h" + +#include "../QueryRetrieveHandler.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class DicomMoveScuJob : public SetOfCommandsJob + { + private: + class Command; + class Unserializer; + + ServerContext& context_; + std::string localAet_; + std::string targetAet_; + RemoteModalityParameters remote_; + std::auto_ptr<DicomUserConnection> connection_; + + void Retrieve(const DicomMap& findAnswer); + + public: + DicomMoveScuJob(ServerContext& context) : + context_(context) + { + } + + DicomMoveScuJob(ServerContext& context, + const Json::Value& serialized); + + void AddFindAnswer(const DicomMap& answer); + + void AddFindAnswer(QueryRetrieveHandler& query, + size_t i); + + const std::string& GetLocalAet() const + { + return localAet_; + } + + void SetLocalAet(const std::string& aet); + + const std::string& GetTargetAet() const + { + return targetAet_; + } + + void SetTargetAet(const std::string& aet); + + const RemoteModalityParameters& GetRemoteModality() const + { + return remote_; + } + + void SetRemoteModality(const RemoteModalityParameters& remote); + + virtual void Stop(JobStopReason reason); + + virtual void GetJobType(std::string& target) + { + target = "DicomMoveScu"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/LuaJobManager.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,271 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "LuaJobManager.h" + +#include "../OrthancInitialization.h" +#include "../../Core/Logging.h" + +#include "../../Core/JobsEngine/Operations/LogJobOperation.h" +#include "Operations/DeleteResourceOperation.h" +#include "Operations/ModifyInstanceOperation.h" +#include "Operations/StorePeerOperation.h" +#include "Operations/StoreScuOperation.h" +#include "Operations/SystemCallOperation.h" + +#include "../../Core/JobsEngine/Operations/NullOperationValue.h" +#include "../../Core/JobsEngine/Operations/StringOperationValue.h" +#include "Operations/DicomInstanceOperationValue.h" + +namespace Orthanc +{ + void LuaJobManager::SignalDone(const SequenceOfOperationsJob& job) + { + boost::mutex::scoped_lock lock(mutex_); + + if (&job == currentJob_) + { + currentId_.clear(); + currentJob_ = NULL; + } + } + + + LuaJobManager::LuaJobManager() : + currentJob_(NULL), + maxOperations_(1000), + priority_(0), + trailingTimeout_(5000) + { + dicomTimeout_ = Configuration::GetGlobalUnsignedIntegerParameter("DicomAssociationCloseDelay", 5); + LOG(INFO) << "Lua: DICOM associations will be closed after " + << dicomTimeout_ << " seconds of inactivity"; + } + + + void LuaJobManager::SetMaxOperationsPerJob(size_t count) + { + boost::mutex::scoped_lock lock(mutex_); + maxOperations_ = count; + } + + + void LuaJobManager::SetPriority(int priority) + { + boost::mutex::scoped_lock lock(mutex_); + priority_ = priority; + } + + + void LuaJobManager::SetTrailingOperationTimeout(unsigned int timeout) + { + boost::mutex::scoped_lock lock(mutex_); + trailingTimeout_ = timeout; + } + + + void LuaJobManager::AwakeTrailingSleep() + { + boost::mutex::scoped_lock lock(mutex_); + + LOG(INFO) << "Awaking trailing sleep"; + + if (currentJob_ != NULL) + { + currentJob_->AwakeTrailingSleep(); + } + } + + + LuaJobManager::Lock::Lock(LuaJobManager& that, + JobsEngine& engine) : + that_(that), + lock_(that.mutex_), + engine_(engine) + { + if (that_.currentJob_ == NULL) + { + isNewJob_ = true; + } + else + { + jobLock_.reset(new SequenceOfOperationsJob::Lock(*that_.currentJob_)); + + if (jobLock_->IsDone() || + jobLock_->GetOperationsCount() >= that_.maxOperations_) + { + jobLock_.reset(NULL); + isNewJob_ = true; + } + else + { + isNewJob_ = false; + } + } + + if (isNewJob_) + { + // Need to create a new job, as the previous one is either + // finished, or is getting too long + that_.currentJob_ = new SequenceOfOperationsJob; + that_.currentJob_->SetDescription("Lua"); + + { + jobLock_.reset(new SequenceOfOperationsJob::Lock(*that_.currentJob_)); + jobLock_->SetTrailingOperationTimeout(that_.trailingTimeout_); + jobLock_->SetDicomAssociationTimeout(that_.dicomTimeout_ * 1000); // Milliseconds expected + } + } + + assert(jobLock_.get() != NULL); + } + + + LuaJobManager::Lock::~Lock() + { + bool isEmpty; + + assert(jobLock_.get() != NULL); + isEmpty = (isNewJob_ && + jobLock_->GetOperationsCount() == 0); + + jobLock_.reset(NULL); + + if (isNewJob_) + { + if (isEmpty) + { + // No operation was added, discard the newly created job + isNewJob_ = false; + delete that_.currentJob_; + that_.currentJob_ = NULL; + } + else + { + engine_.GetRegistry().Submit(that_.currentId_, that_.currentJob_, that_.priority_); + } + } + } + + + size_t LuaJobManager::Lock::AddDeleteResourceOperation(ServerContext& context) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new DeleteResourceOperation(context)); + } + + + size_t LuaJobManager::Lock::AddLogOperation() + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new LogJobOperation); + } + + + size_t LuaJobManager::Lock::AddStoreScuOperation(const std::string& localAet, + const RemoteModalityParameters& modality) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new StoreScuOperation(localAet, modality)); + } + + + size_t LuaJobManager::Lock::AddStorePeerOperation(const WebServiceParameters& peer) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new StorePeerOperation(peer)); + } + + + size_t LuaJobManager::Lock::AddSystemCallOperation(const std::string& command) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation(new SystemCallOperation(command)); + } + + + size_t LuaJobManager::Lock::AddSystemCallOperation + (const std::string& command, + const std::vector<std::string>& preArguments, + const std::vector<std::string>& postArguments) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation + (new SystemCallOperation(command, preArguments, postArguments)); + } + + + size_t LuaJobManager::Lock::AddModifyInstanceOperation(ServerContext& context, + DicomModification* modification) + { + assert(jobLock_.get() != NULL); + return jobLock_->AddOperation + (new ModifyInstanceOperation(context, RequestOrigin_Lua, modification)); + } + + + void LuaJobManager::Lock::AddNullInput(size_t operation) + { + assert(jobLock_.get() != NULL); + NullOperationValue null; + jobLock_->AddInput(operation, null); + } + + + void LuaJobManager::Lock::AddStringInput(size_t operation, + const std::string& content) + { + assert(jobLock_.get() != NULL); + StringOperationValue value(content); + jobLock_->AddInput(operation, value); + } + + + void LuaJobManager::Lock::AddDicomInstanceInput(size_t operation, + ServerContext& context, + const std::string& instanceId) + { + assert(jobLock_.get() != NULL); + DicomInstanceOperationValue value(context, instanceId); + jobLock_->AddInput(operation, value); + } + + + void LuaJobManager::Lock::Connect(size_t operation1, + size_t operation2) + { + assert(jobLock_.get() != NULL); + jobLock_->Connect(operation1, operation2); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/LuaJobManager.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/DicomParsing/DicomModification.h" +#include "../../Core/JobsEngine/JobsEngine.h" +#include "../../Core/JobsEngine/Operations/SequenceOfOperationsJob.h" +#include "../../Core/WebServiceParameters.h" + +namespace Orthanc +{ + class ServerContext; + + class LuaJobManager : private SequenceOfOperationsJob::IObserver + { + private: + boost::mutex mutex_; + std::string currentId_; + SequenceOfOperationsJob* currentJob_; + size_t maxOperations_; + int priority_; + unsigned int trailingTimeout_; + unsigned int dicomTimeout_; + + virtual void SignalDone(const SequenceOfOperationsJob& job); + + public: + LuaJobManager(); + + void SetMaxOperationsPerJob(size_t count); + + void SetPriority(int priority); + + void SetTrailingOperationTimeout(unsigned int timeout); + + void AwakeTrailingSleep(); + + class Lock : public boost::noncopyable + { + private: + LuaJobManager& that_; + boost::mutex::scoped_lock lock_; + JobsEngine& engine_; + std::auto_ptr<SequenceOfOperationsJob::Lock> jobLock_; + bool isNewJob_; + + public: + Lock(LuaJobManager& that, + JobsEngine& engine); + + ~Lock(); + + size_t AddLogOperation(); + + size_t AddDeleteResourceOperation(ServerContext& context); + + size_t AddStoreScuOperation(const std::string& localAet, + const RemoteModalityParameters& modality); + + size_t AddStorePeerOperation(const WebServiceParameters& peer); + + size_t AddSystemCallOperation(const std::string& command); + + size_t AddSystemCallOperation(const std::string& command, + const std::vector<std::string>& preArguments, + const std::vector<std::string>& postArguments); + + size_t AddModifyInstanceOperation(ServerContext& context, + DicomModification* modification); + + void AddNullInput(size_t operation); + + void AddStringInput(size_t operation, + const std::string& content); + + void AddDicomInstanceInput(size_t operation, + ServerContext& context, + const std::string& instanceId); + + void Connect(size_t operation1, + size_t operation2); + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/MergeStudyJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,395 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "MergeStudyJob.h" + +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" + + +namespace Orthanc +{ + void MergeStudyJob::AddSourceSeriesInternal(const std::string& series) + { + // Generate a target SeriesInstanceUID for this series + seriesUidMap_[series] = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series); + + // Add all the instances of the series as to be processed + std::list<std::string> instances; + context_.GetIndex().GetChildren(instances, series); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + AddInstance(*it); + } + } + + + void MergeStudyJob::AddSourceStudyInternal(const std::string& study) + { + if (study == targetStudy_) + { + LOG(ERROR) << "Cannot merge a study into the same study: " << study; + throw OrthancException(ErrorCode_UnknownResource); + } + else + { + std::list<std::string> series; + context_.GetIndex().GetChildren(series, study); + + for (std::list<std::string>::const_iterator + it = series.begin(); it != series.end(); ++it) + { + AddSourceSeriesInternal(*it); + } + } + } + + + bool MergeStudyJob::HandleInstance(const std::string& instance) + { + /** + * Retrieve the DICOM instance to be modified + **/ + + std::auto_ptr<ParsedDicomFile> modified; + + try + { + ServerContext::DicomCacheLocker locker(context_, instance); + modified.reset(locker.GetDicom().Clone(true)); + } + catch (OrthancException&) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + + /** + * Chose the target UIDs + **/ + + std::string series = modified->GetHasher().HashSeries(); + + SeriesUidMap::const_iterator targetSeriesUid = seriesUidMap_.find(series); + + if (targetSeriesUid == seriesUidMap_.end()) + { + throw OrthancException(ErrorCode_BadFileFormat); // Should never happen + } + + + /** + * Copy the tags from the "Patient Module Attributes" and "General + * Study Module Attributes" modules of the target study + **/ + + for (std::set<DicomTag>::const_iterator it = removals_.begin(); + it != removals_.end(); ++it) + { + modified->Remove(*it); + } + + for (Replacements::const_iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + modified->ReplacePlainString(it->first, it->second); + } + + + /** + * Store the new instance into Orthanc + **/ + + modified->ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, targetSeriesUid->second); + + DicomInstanceToStore toStore; + toStore.SetOrigin(origin_); + toStore.SetParsedDicomFile(*modified); + + std::string modifiedInstance; + if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + { + LOG(ERROR) << "Error while storing a modified instance " << instance; + return false; + } + + return true; + } + + + bool MergeStudyJob::HandleTrailingStep() + { + if (!keepSource_) + { + const size_t n = GetInstancesCount(); + + for (size_t i = 0; i < n; i++) + { + Json::Value tmp; + context_.DeleteResource(tmp, GetInstance(i), ResourceType_Instance); + } + } + + return true; + } + + + MergeStudyJob::MergeStudyJob(ServerContext& context, + const std::string& targetStudy) : + context_(context), + keepSource_(false), + targetStudy_(targetStudy) + { + /** + * Check the validity of the input ID + **/ + + ResourceType type; + + if (!context_.GetIndex().LookupResourceType(type, targetStudy) || + type != ResourceType_Study) + { + LOG(ERROR) << "Cannot merge into an unknown study: " << targetStudy; + throw OrthancException(ErrorCode_UnknownResource); + } + + + /** + * Detect the tags to be removed/replaced by parsing one child + * instance of the study + **/ + + DicomTag::AddTagsForModule(removals_, DicomModule_Patient); + DicomTag::AddTagsForModule(removals_, DicomModule_Study); + + std::list<std::string> instances; + context_.GetIndex().GetChildInstances(instances, targetStudy); + + if (instances.empty()) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + DicomMap dicom; + + { + ServerContext::DicomCacheLocker locker(context_, instances.front()); + locker.GetDicom().ExtractDicomSummary(dicom); + } + + const std::set<DicomTag> moduleTags = removals_; + for (std::set<DicomTag>::const_iterator it = moduleTags.begin(); + it != moduleTags.end(); ++it) + { + const DicomValue* value = dicom.TestAndGetValue(*it); + std::string str; + + if (value != NULL && + value->CopyToString(str, false)) + { + removals_.erase(*it); + replacements_.insert(std::make_pair(*it, str)); + } + } + } + + + void MergeStudyJob::SetOrigin(const DicomInstanceOrigin& origin) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + origin_ = origin; + } + } + + + void MergeStudyJob::SetOrigin(const RestApiCall& call) + { + SetOrigin(DicomInstanceOrigin::FromRest(call)); + } + + + void MergeStudyJob::AddSource(const std::string& studyOrSeries) + { + ResourceType level; + + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (!context_.GetIndex().LookupResourceType(level, studyOrSeries)) + { + LOG(ERROR) << "Cannot find this resource: " << studyOrSeries; + throw OrthancException(ErrorCode_UnknownResource); + } + else + { + switch (level) + { + case ResourceType_Study: + AddSourceStudyInternal(studyOrSeries); + break; + + case ResourceType_Series: + AddSourceSeries(studyOrSeries); + break; + + default: + LOG(ERROR) << "This resource is neither a study, nor a series: " + << studyOrSeries << " is a " << EnumerationToString(level); + throw OrthancException(ErrorCode_UnknownResource); + } + } + } + + + void MergeStudyJob::AddSourceSeries(const std::string& series) + { + std::string parent; + + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (!context_.GetIndex().LookupParent(parent, series, ResourceType_Study)) + { + LOG(ERROR) << "This resource is not a series: " << series; + throw OrthancException(ErrorCode_UnknownResource); + } + else if (parent == targetStudy_) + { + LOG(ERROR) << "Cannot merge series " << series + << " into its parent study " << targetStudy_; + throw OrthancException(ErrorCode_UnknownResource); + } + else + { + AddSourceSeriesInternal(series); + } + } + + + void MergeStudyJob::AddSourceStudy(const std::string& study) + { + ResourceType actualLevel; + + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (!context_.GetIndex().LookupResourceType(actualLevel, study) || + actualLevel != ResourceType_Study) + { + LOG(ERROR) << "This resource is not a study: " << study; + throw OrthancException(ErrorCode_UnknownResource); + } + else + { + AddSourceStudyInternal(study); + } + } + + + void MergeStudyJob::SetKeepSource(bool keep) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + keepSource_ = keep; + } + + + void MergeStudyJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + value["TargetStudy"] = targetStudy_; + } + + + static const char* KEEP_SOURCE = "KeepSource"; + static const char* TARGET_STUDY = "TargetStudy"; + static const char* REPLACEMENTS = "Replacements"; + static const char* REMOVALS = "Removals"; + static const char* SERIES_UID_MAP = "SeriesUIDMap"; + static const char* ORIGIN = "Origin"; + + + MergeStudyJob::MergeStudyJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), // (*) + context_(context) + { + if (!HasTrailingStep()) + { + // Should have been set by (*) + throw OrthancException(ErrorCode_InternalError); + } + + keepSource_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOURCE); + targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY); + SerializationToolbox::ReadMapOfTags(replacements_, serialized, REPLACEMENTS); + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); + SerializationToolbox::ReadMapOfStrings(seriesUidMap_, serialized, SERIES_UID_MAP); + origin_ = DicomInstanceOrigin(serialized[ORIGIN]); + } + + + bool MergeStudyJob::Serialize(Json::Value& target) + { + if (!SetOfInstancesJob::Serialize(target)) + { + return false; + } + else + { + target[KEEP_SOURCE] = keepSource_; + target[TARGET_STUDY] = targetStudy_; + SerializationToolbox::WriteMapOfTags(target, replacements_, REPLACEMENTS); + SerializationToolbox::WriteSetOfTags(target, removals_, REMOVALS); + SerializationToolbox::WriteMapOfStrings(target, seriesUidMap_, SERIES_UID_MAP); + origin_.Serialize(target[ORIGIN]); + + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/MergeStudyJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/SetOfInstancesJob.h" + +#include "../ServerContext.h" + +namespace Orthanc +{ + class MergeStudyJob : public SetOfInstancesJob + { + private: + typedef std::map<std::string, std::string> SeriesUidMap; + typedef std::map<DicomTag, std::string> Replacements; + + + ServerContext& context_; + bool keepSource_; + std::string targetStudy_; + Replacements replacements_; + std::set<DicomTag> removals_; + SeriesUidMap seriesUidMap_; + DicomInstanceOrigin origin_; + + + void AddSourceSeriesInternal(const std::string& series); + + void AddSourceStudyInternal(const std::string& study); + + + protected: + virtual bool HandleInstance(const std::string& instance); + + virtual bool HandleTrailingStep(); + + public: + MergeStudyJob(ServerContext& context, + const std::string& targetStudy); + + MergeStudyJob(ServerContext& context, + const Json::Value& serialized); + + const std::string& GetTargetStudy() const + { + return targetStudy_; + } + + void AddSource(const std::string& studyOrSeries); + + void AddSourceStudy(const std::string& study); + + void AddSourceSeries(const std::string& series); + + bool IsKeepSource() const + { + return keepSource_; + } + + void SetKeepSource(bool keep); + + void SetOrigin(const DicomInstanceOrigin& origin); + + void SetOrigin(const RestApiCall& call); + + const DicomInstanceOrigin& GetOrigin() const + { + return origin_; + } + + virtual void Stop(JobStopReason reason) + { + } + + virtual void GetJobType(std::string& target) + { + target = "MergeStudy"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,72 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DeleteResourceOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" + +namespace Orthanc +{ + void DeleteResourceOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + switch (input.GetType()) + { + case JobOperationValue::Type_DicomInstance: + { + const DicomInstanceOperationValue& instance = dynamic_cast<const DicomInstanceOperationValue&>(input); + LOG(INFO) << "Lua: Deleting instance: " << instance.GetId(); + + try + { + Json::Value tmp; + context_.DeleteResource(tmp, instance.GetId(), ResourceType_Instance); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to delete instance " << instance.GetId() << ": " << e.What(); + } + + break; + } + + default: + break; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/Operations/IJobOperation.h" + +#include "../../ServerContext.h" + +namespace Orthanc +{ + class DeleteResourceOperation : public IJobOperation + { + private: + ServerContext& context_; + + public: + DeleteResourceOperation(ServerContext& context) : + context_(context) + { + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "DeleteResource"; + } + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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/JobsEngine/Operations/JobOperationValue.h" + +#include "../../ServerContext.h" + +namespace Orthanc +{ + class DicomInstanceOperationValue : public JobOperationValue + { + private: + ServerContext& context_; + std::string id_; + + public: + DicomInstanceOperationValue(ServerContext& context, + const std::string& id) : + JobOperationValue(Type_DicomInstance), + context_(context), + id_(id) + { + } + + ServerContext& GetServerContext() const + { + return context_; + } + + const std::string& GetId() const + { + return id_; + } + + void ReadDicom(std::string& dicom) const + { + context_.ReadDicom(dicom, id_); + } + + virtual JobOperationValue* Clone() const + { + return new DicomInstanceOperationValue(context_, id_); + } + + virtual void Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "DicomInstance"; + target["ID"] = id_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,153 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ModifyInstanceOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + ModifyInstanceOperation::ModifyInstanceOperation(ServerContext& context, + RequestOrigin origin, + DicomModification* modification) : + context_(context), + origin_(origin), + modification_(modification) + { + if (modification == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + modification_->SetAllowManualIdentifiers(true); + + if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) + { + modification_->SetLevel(ResourceType_Patient); + } + else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Study); + } + else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Series); + } + else + { + modification_->SetLevel(ResourceType_Instance); + } + + if (origin_ != RequestOrigin_Lua) + { + // TODO If issued from HTTP, "remoteIp" and "username" must be provided + throw OrthancException(ErrorCode_NotImplemented); + } + } + + void ModifyInstanceOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + if (input.GetType() != JobOperationValue::Type_DicomInstance) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + LOG(INFO) << "Lua: Modifying instance " << instance.GetId(); + + std::auto_ptr<ParsedDicomFile> modified; + + { + ServerContext::DicomCacheLocker lock(context_, instance.GetId()); + modified.reset(lock.GetDicom().Clone(true)); + } + + try + { + modification_->Apply(*modified); + + DicomInstanceToStore toStore; + assert(origin_ == RequestOrigin_Lua); + toStore.SetOrigin(DicomInstanceOrigin::FromLua()); + toStore.SetParsedDicomFile(*modified); + + // TODO other metadata + toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, instance.GetId()); + + std::string modifiedId; + context_.Store(modifiedId, toStore); + + // Only chain with other commands if this command succeeds + outputs.Append(new DicomInstanceOperationValue(instance.GetServerContext(), modifiedId)); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to modify instance " << instance.GetId() + << ": " << e.What(); + } + } + + + void ModifyInstanceOperation::Serialize(Json::Value& target) const + { + target = Json::objectValue; + target["Type"] = "ModifyInstance"; + target["Origin"] = EnumerationToString(origin_); + modification_->Serialize(target["Modification"]); + } + + + ModifyInstanceOperation::ModifyInstanceOperation(ServerContext& context, + const Json::Value& serialized) : + context_(context) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "ModifyInstance" || + !serialized.isMember("Modification")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + origin_ = StringToRequestOrigin(SerializationToolbox::ReadString(serialized, "Origin")); + + modification_.reset(new DicomModification(serialized["Modification"])); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/Operations/IJobOperation.h" +#include "../../../Core/DicomParsing/DicomModification.h" + +#include "../../ServerContext.h" + +namespace Orthanc +{ + class ModifyInstanceOperation : public IJobOperation + { + private: + ServerContext& context_; + RequestOrigin origin_; + std::auto_ptr<DicomModification> modification_; + + public: + ModifyInstanceOperation(ServerContext& context, + RequestOrigin origin, + DicomModification* modification); // Takes ownership + + ModifyInstanceOperation(ServerContext& context, + const Json::Value& serialized); + + const RequestOrigin& GetRequestOrigin() const + { + return origin_; + } + + const DicomModification& GetModification() const + { + return *modification_; + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& target) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,106 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "StorePeerOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" +#include "../../../Core/HttpClient.h" +#include "../../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + void StorePeerOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + // Configure the HTTP client + HttpClient client(peer_, "instances"); + client.SetMethod(HttpMethod_Post); + + if (input.GetType() != JobOperationValue::Type_DicomInstance) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + LOG(INFO) << "Lua: Sending instance " << instance.GetId() << " to Orthanc peer \"" + << peer_.GetUrl() << "\""; + + try + { + instance.ReadDicom(client.GetBody()); + + std::string answer; + if (!client.Apply(answer)) + { + LOG(ERROR) << "Lua: Unable to send instance " << instance.GetId() + << " to Orthanc peer \"" << peer_.GetUrl(); + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to send instance " << instance.GetId() + << " to Orthanc peer \"" << peer_.GetUrl() << "\": " << e.What(); + } + + outputs.Append(input.Clone()); + } + + + void StorePeerOperation::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "StorePeer"; + peer_.Serialize(result["Peer"], + true /* force advanced format */, + true /* include passwords */); + } + + + StorePeerOperation::StorePeerOperation(const Json::Value& serialized) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "StorePeer" || + !serialized.isMember("Peer")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + peer_ = WebServiceParameters(serialized["Peer"]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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/JobsEngine/Operations/IJobOperation.h" +#include "../../../Core/WebServiceParameters.h" + +namespace Orthanc +{ + class StorePeerOperation : public IJobOperation + { + private: + WebServiceParameters peer_; + + public: + StorePeerOperation(const WebServiceParameters& peer) : + peer_(peer) + { + } + + StorePeerOperation(const Json::Value& serialized); + + const WebServiceParameters& GetPeer() const + { + return peer_; + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "StoreScuOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" +#include "../../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + void StoreScuOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + std::auto_ptr<IDicomConnectionManager::IResource> resource + (connectionManager.AcquireConnection(localAet_, modality_)); + + if (resource.get() == NULL) + { + LOG(ERROR) << "Lua: Cannot connect to modality: " << modality_.GetApplicationEntityTitle(); + return; + } + + if (input.GetType() != JobOperationValue::Type_DicomInstance) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + LOG(INFO) << "Lua: Sending instance " << instance.GetId() << " to modality \"" + << modality_.GetApplicationEntityTitle() << "\""; + + try + { + std::string dicom; + instance.ReadDicom(dicom); + resource->GetConnection().Store(dicom); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Unable to send instance " << instance.GetId() << " to modality \"" + << modality_.GetApplicationEntityTitle() << "\": " << e.What(); + } + + outputs.Append(input.Clone()); + } + + + void StoreScuOperation::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "StoreScu"; + result["LocalAET"] = localAet_; + modality_.Serialize(result["Modality"], true /* force advanced format */); + } + + + StoreScuOperation::StoreScuOperation(const Json::Value& serialized) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "StoreScu" || + !serialized.isMember("LocalAET")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + localAet_ = SerializationToolbox::ReadString(serialized, "LocalAET"); + modality_ = RemoteModalityParameters(serialized["Modality"]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,74 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/Operations/IJobOperation.h" +#include "../../../Core/DicomNetworking/RemoteModalityParameters.h" + +namespace Orthanc +{ + class StoreScuOperation : public IJobOperation + { + private: + std::string localAet_; + RemoteModalityParameters modality_; + + public: + StoreScuOperation(const std::string& localAet, + const RemoteModalityParameters& modality) : + localAet_(localAet), + modality_(modality) + { + } + + StoreScuOperation(const Json::Value& serialized); + + const std::string& GetLocalAet() const + { + return localAet_; + } + + const RemoteModalityParameters& GetRemoteModality() const + { + return modality_; + } + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& manager); + + virtual void Serialize(Json::Value& result) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,165 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SystemCallOperation.h" + +#include "DicomInstanceOperationValue.h" + +#include "../../../Core/JobsEngine/Operations/StringOperationValue.h" +#include "../../../Core/Logging.h" +#include "../../../Core/OrthancException.h" +#include "../../../Core/SerializationToolbox.h" +#include "../../../Core/TemporaryFile.h" +#include "../../../Core/Toolbox.h" + +namespace Orthanc +{ + const std::string& SystemCallOperation::GetPreArgument(size_t i) const + { + if (i >= preArguments_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return preArguments_[i]; + } + } + + + const std::string& SystemCallOperation::GetPostArgument(size_t i) const + { + if (i >= postArguments_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return postArguments_[i]; + } + } + + + void SystemCallOperation::Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager) + { + std::vector<std::string> arguments = preArguments_; + + arguments.reserve(arguments.size() + postArguments_.size() + 1); + + std::auto_ptr<TemporaryFile> tmp; + + switch (input.GetType()) + { + case JobOperationValue::Type_DicomInstance: + { + const DicomInstanceOperationValue& instance = + dynamic_cast<const DicomInstanceOperationValue&>(input); + + std::string dicom; + instance.ReadDicom(dicom); + + tmp.reset(new TemporaryFile); + tmp->Write(dicom); + + arguments.push_back(tmp->GetPath()); + break; + } + + case JobOperationValue::Type_String: + { + const StringOperationValue& value = + dynamic_cast<const StringOperationValue&>(input); + + arguments.push_back(value.GetContent()); + break; + } + + case JobOperationValue::Type_Null: + break; + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + + for (size_t i = 0; i < postArguments_.size(); i++) + { + arguments.push_back(postArguments_[i]); + } + + std::string info = command_; + for (size_t i = 0; i < arguments.size(); i++) + { + info += " " + arguments[i]; + } + + LOG(INFO) << "Lua: System call: \"" << info << "\""; + + try + { + SystemToolbox::ExecuteSystemCommand(command_, arguments); + + // Only chain with other commands if this operation succeeds + outputs.Append(input.Clone()); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: Failed system call - \"" << info << "\": " << e.What(); + } + } + + + void SystemCallOperation::Serialize(Json::Value& result) const + { + result = Json::objectValue; + result["Type"] = "SystemCall"; + result["Command"] = command_; + SerializationToolbox::WriteArrayOfStrings(result, preArguments_, "PreArguments"); + SerializationToolbox::WriteArrayOfStrings(result, postArguments_, "PostArguments"); + } + + + SystemCallOperation::SystemCallOperation(const Json::Value& serialized) + { + if (SerializationToolbox::ReadString(serialized, "Type") != "SystemCall") + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + command_ = SerializationToolbox::ReadString(serialized, "Command"); + SerializationToolbox::ReadArrayOfStrings(preArguments_, serialized, "PreArguments"); + SerializationToolbox::ReadArrayOfStrings(postArguments_, serialized, "PostArguments"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,102 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/Operations/IJobOperation.h" + +#include <string> + +namespace Orthanc +{ + class SystemCallOperation : public IJobOperation + { + private: + std::string command_; + std::vector<std::string> preArguments_; + std::vector<std::string> postArguments_; + + public: + SystemCallOperation(const std::string& command) : + command_(command) + { + } + + SystemCallOperation(const Json::Value& serialized); + + SystemCallOperation(const std::string& command, + const std::vector<std::string>& preArguments, + const std::vector<std::string>& postArguments) : + command_(command), + preArguments_(preArguments), + postArguments_(postArguments) + { + } + + void AddPreArgument(const std::string& argument) + { + preArguments_.push_back(argument); + } + + void AddPostArgument(const std::string& argument) + { + postArguments_.push_back(argument); + } + + const std::string& GetCommand() const + { + return command_; + } + + size_t GetPreArgumentsCount() const + { + return preArguments_.size(); + } + + size_t GetPostArgumentsCount() const + { + return postArguments_.size(); + } + + const std::string& GetPreArgument(size_t i) const; + + const std::string& GetPostArgument(size_t i) const; + + virtual void Apply(JobOperationValues& outputs, + const JobOperationValue& input, + IDicomConnectionManager& connectionManager); + + virtual void Serialize(Json::Value& result) const; + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "OrthancJobUnserializer.h" + +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" +#include "../../Core/SerializationToolbox.h" + +#include "Operations/DeleteResourceOperation.h" +#include "Operations/DicomInstanceOperationValue.h" +#include "Operations/ModifyInstanceOperation.h" +#include "Operations/StorePeerOperation.h" +#include "Operations/StoreScuOperation.h" +#include "Operations/SystemCallOperation.h" + +#include "DicomModalityStoreJob.h" +#include "DicomMoveScuJob.h" +#include "OrthancPeerStoreJob.h" +#include "ResourceModificationJob.h" +#include "MergeStudyJob.h" +#include "SplitStudyJob.h" + +namespace Orthanc +{ + IJob* OrthancJobUnserializer::UnserializeJob(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + +#if ORTHANC_ENABLE_PLUGINS == 1 + if (context_.HasPlugins()) + { + std::auto_ptr<IJob> job(context_.GetPlugins().UnserializeJob(type, source)); + if (job.get() != NULL) + { + return job.release(); + } + } +#endif + + if (type == "DicomModalityStore") + { + return new DicomModalityStoreJob(context_, source); + } + else if (type == "OrthancPeerStore") + { + return new OrthancPeerStoreJob(context_, source); + } + else if (type == "ResourceModification") + { + return new ResourceModificationJob(context_, source); + } + else if (type == "MergeStudy") + { + return new MergeStudyJob(context_, source); + } + else if (type == "SplitStudy") + { + return new SplitStudyJob(context_, source); + } + else if (type == "DicomMoveScu") + { + return new DicomMoveScuJob(context_, source); + } + else + { + return GenericJobUnserializer::UnserializeJob(source); + } + } + + + IJobOperation* OrthancJobUnserializer::UnserializeOperation(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "DeleteResource") + { + return new DeleteResourceOperation(context_); + } + else if (type == "ModifyInstance") + { + return new ModifyInstanceOperation(context_, source); + } + else if (type == "StorePeer") + { + return new StorePeerOperation(source); + } + else if (type == "StoreScu") + { + return new StoreScuOperation(source); + } + else if (type == "SystemCall") + { + return new SystemCallOperation(source); + } + else + { + return GenericJobUnserializer::UnserializeOperation(source); + } + } + + + JobOperationValue* OrthancJobUnserializer::UnserializeValue(const Json::Value& source) + { + const std::string type = SerializationToolbox::ReadString(source, "Type"); + + if (type == "DicomInstance") + { + return new DicomInstanceOperationValue(context_, SerializationToolbox::ReadString(source, "ID")); + } + else + { + return GenericJobUnserializer::UnserializeValue(source); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../ServerContext.h" +#include "../../Core/JobsEngine/GenericJobUnserializer.h" + +namespace Orthanc +{ + class OrthancJobUnserializer : public GenericJobUnserializer + { + private: + ServerContext& context_; + + public: + OrthancJobUnserializer(ServerContext& context) : + context_(context) + { + } + + virtual IJob* UnserializeJob(const Json::Value& value); + + virtual IJobOperation* UnserializeOperation(const Json::Value& value); + + virtual JobOperationValue* UnserializeValue(const Json::Value& value); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,139 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "OrthancPeerStoreJob.h" + +#include "../../Core/Logging.h" + + +namespace Orthanc +{ + bool OrthancPeerStoreJob::HandleInstance(const std::string& instance) + { + //boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + if (client_.get() == NULL) + { + client_.reset(new HttpClient(peer_, "instances")); + client_->SetMethod(HttpMethod_Post); + } + + LOG(INFO) << "Sending instance " << instance << " to peer \"" + << peer_.GetUrl() << "\""; + + try + { + context_.ReadDicom(client_->GetBody(), instance); + } + catch (OrthancException& e) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + std::string answer; + if (client_->Apply(answer)) + { + return true; + } + else + { + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + + bool OrthancPeerStoreJob::HandleTrailingStep() + { + throw OrthancException(ErrorCode_InternalError); + } + + + void OrthancPeerStoreJob::SetPeer(const WebServiceParameters& peer) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + peer_ = peer; + } + } + + + void OrthancPeerStoreJob::Stop(JobStopReason reason) // For pausing jobs + { + client_.reset(NULL); + } + + + void OrthancPeerStoreJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + + Json::Value v; + peer_.Serialize(v, + false /* allow simple format if possible */, + false /* don't include passwords */); + value["Peer"] = v; + } + + + static const char* PEER = "Peer"; + + OrthancPeerStoreJob::OrthancPeerStoreJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), + context_(context) + { + peer_ = WebServiceParameters(serialized[PEER]); + } + + + bool OrthancPeerStoreJob::Serialize(Json::Value& target) + { + if (!SetOfInstancesJob::Serialize(target)) + { + return false; + } + else + { + peer_.Serialize(target[PEER], + true /* force advanced format */, + true /* include passwords */); + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/SetOfInstancesJob.h" +#include "../../Core/HttpClient.h" + +#include "../ServerContext.h" + + +namespace Orthanc +{ + class OrthancPeerStoreJob : public SetOfInstancesJob + { + private: + ServerContext& context_; + WebServiceParameters peer_; + std::auto_ptr<HttpClient> client_; + + protected: + virtual bool HandleInstance(const std::string& instance); + + virtual bool HandleTrailingStep(); + + public: + OrthancPeerStoreJob(ServerContext& context) : + context_(context) + { + } + + OrthancPeerStoreJob(ServerContext& context, + const Json::Value& serialize); + + void SetPeer(const WebServiceParameters& peer); + + const WebServiceParameters& GetPeer() const + { + return peer_; + } + + virtual void Stop(JobStopReason reason); // For pausing jobs + + virtual void GetJobType(std::string& target) + { + target = "OrthancPeerStore"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,332 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ResourceModificationJob.h" + +#include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + class ResourceModificationJob::Output : public boost::noncopyable + { + private: + ResourceType level_; + bool isFirst_; + std::string id_; + std::string patientId_; + + public: + Output(ResourceType level) : + level_(level), + isFirst_(true) + { + if (level_ != ResourceType_Patient && + level_ != ResourceType_Study && + level_ != ResourceType_Series) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + ResourceType GetLevel() const + { + return level_; + } + + + void Update(DicomInstanceHasher& hasher) + { + if (isFirst_) + { + switch (level_) + { + case ResourceType_Series: + id_ = hasher.HashSeries(); + break; + + case ResourceType_Study: + id_ = hasher.HashStudy(); + break; + + case ResourceType_Patient: + id_ = hasher.HashPatient(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + patientId_ = hasher.HashPatient(); + isFirst_ = false; + } + } + + + bool Format(Json::Value& target) + { + if (isFirst_) + { + return false; + } + else + { + target = Json::objectValue; + target["Type"] = EnumerationToString(level_); + target["ID"] = id_; + target["Path"] = GetBasePath(level_, id_); + target["PatientID"] = patientId_; + return true; + } + } + + + bool GetIdentifier(std::string& id) + { + if (isFirst_) + { + return false; + } + else + { + id = id_; + return true; + } + } + }; + + + + + bool ResourceModificationJob::HandleInstance(const std::string& instance) + { + if (modification_.get() == NULL || + output_.get() == NULL) + { + LOG(ERROR) << "No modification was provided for this job"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + + LOG(INFO) << "Modifying instance in a job: " << instance; + + + /** + * Retrieve the original instance from the DICOM cache. + **/ + + std::auto_ptr<DicomInstanceHasher> originalHasher; + std::auto_ptr<ParsedDicomFile> modified; + + try + { + ServerContext::DicomCacheLocker locker(context_, instance); + ParsedDicomFile& original = locker.GetDicom(); + + originalHasher.reset(new DicomInstanceHasher(original.GetHasher())); + modified.reset(original.Clone(true)); + } + catch (OrthancException&) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + + /** + * Compute the resulting DICOM instance. + **/ + + modification_->Apply(*modified); + + DicomInstanceToStore toStore; + toStore.SetOrigin(origin_); + toStore.SetParsedDicomFile(*modified); + + + /** + * Prepare the metadata information to associate with the + * resulting DICOM instance (AnonymizedFrom/ModifiedFrom). + **/ + + DicomInstanceHasher modifiedHasher = modified->GetHasher(); + + MetadataType metadataType = (isAnonymization_ ? + MetadataType_AnonymizedFrom : + MetadataType_ModifiedFrom); + + if (originalHasher->HashSeries() != modifiedHasher.HashSeries()) + { + toStore.AddMetadata(ResourceType_Series, metadataType, originalHasher->HashSeries()); + } + + if (originalHasher->HashStudy() != modifiedHasher.HashStudy()) + { + toStore.AddMetadata(ResourceType_Study, metadataType, originalHasher->HashStudy()); + } + + if (originalHasher->HashPatient() != modifiedHasher.HashPatient()) + { + toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher->HashPatient()); + } + + assert(instance == originalHasher->HashInstance()); + toStore.AddMetadata(ResourceType_Instance, metadataType, instance); + + + /** + * Store the resulting DICOM instance into the Orthanc store. + **/ + + std::string modifiedInstance; + if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + { + LOG(ERROR) << "Error while storing a modified instance " << instance; + throw OrthancException(ErrorCode_CannotStoreInstance); + } + + assert(modifiedInstance == modifiedHasher.HashInstance()); + + output_->Update(modifiedHasher); + + return true; + } + + + bool ResourceModificationJob::HandleTrailingStep() + { + throw OrthancException(ErrorCode_InternalError); + } + + + void ResourceModificationJob::SetModification(DicomModification* modification, + ResourceType level, + bool isAnonymization) + { + if (modification == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + modification_.reset(modification); + output_.reset(new Output(level)); + isAnonymization_ = isAnonymization; + } + } + + + void ResourceModificationJob::SetOrigin(const DicomInstanceOrigin& origin) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + origin_ = origin; + } + } + + + void ResourceModificationJob::SetOrigin(const RestApiCall& call) + { + SetOrigin(DicomInstanceOrigin::FromRest(call)); + } + + + const DicomModification& ResourceModificationJob::GetModification() const + { + if (modification_.get() == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *modification_; + } + } + + + void ResourceModificationJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + + value["IsAnonymization"] = isAnonymization_; + + if (output_.get() != NULL) + { + output_->Format(value); + } + } + + + static const char* MODIFICATION = "Modification"; + static const char* ORIGIN = "Origin"; + static const char* IS_ANONYMIZATION = "IsAnonymization"; + + + ResourceModificationJob::ResourceModificationJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), + context_(context) + { + isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); + origin_ = DicomInstanceOrigin(serialized[ORIGIN]); + modification_.reset(new DicomModification(serialized[MODIFICATION])); + } + + bool ResourceModificationJob::Serialize(Json::Value& value) + { + if (!SetOfInstancesJob::Serialize(value)) + { + return false; + } + else + { + value[IS_ANONYMIZATION] = isAnonymization_; + origin_.Serialize(value[ORIGIN]); + + Json::Value tmp; + modification_->Serialize(tmp); + value[MODIFICATION] = tmp; + + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,100 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/SetOfInstancesJob.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class ResourceModificationJob : public SetOfInstancesJob + { + private: + class Output; + + ServerContext& context_; + std::auto_ptr<DicomModification> modification_; + boost::shared_ptr<Output> output_; + bool isAnonymization_; + DicomInstanceOrigin origin_; + + protected: + virtual bool HandleInstance(const std::string& instance); + + virtual bool HandleTrailingStep(); + + public: + ResourceModificationJob(ServerContext& context) : + context_(context), + isAnonymization_(false) + { + } + + ResourceModificationJob(ServerContext& context, + const Json::Value& serialized); + + void SetModification(DicomModification* modification, // Takes ownership + ResourceType level, + bool isAnonymization); + + void SetOrigin(const DicomInstanceOrigin& origin); + + void SetOrigin(const RestApiCall& call); + + const DicomModification& GetModification() const; + + bool IsAnonymization() const + { + return isAnonymization_; + } + + const DicomInstanceOrigin& GetOrigin() const + { + return origin_; + } + + virtual void Stop(JobStopReason reason) + { + } + + virtual void GetJobType(std::string& target) + { + target = "ResourceModification"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,370 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SplitStudyJob.h" + +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" + +namespace Orthanc +{ + void SplitStudyJob::CheckAllowedTag(const DicomTag& tag) const + { + if (allowedTags_.find(tag) == allowedTags_.end()) + { + LOG(ERROR) << "Cannot modify the following tag while splitting a study " + << "(not in the patient/study modules): " + << FromDcmtkBridge::GetTagName(tag, "") << " (" << tag.Format() << ")"; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + void SplitStudyJob::Setup() + { + SetPermissive(false); + + DicomTag::AddTagsForModule(allowedTags_, DicomModule_Patient); + DicomTag::AddTagsForModule(allowedTags_, DicomModule_Study); + allowedTags_.erase(DICOM_TAG_STUDY_INSTANCE_UID); + allowedTags_.erase(DICOM_TAG_SERIES_INSTANCE_UID); + } + + + bool SplitStudyJob::HandleInstance(const std::string& instance) + { + /** + * Retrieve the DICOM instance to be modified + **/ + + std::auto_ptr<ParsedDicomFile> modified; + + try + { + ServerContext::DicomCacheLocker locker(context_, instance); + modified.reset(locker.GetDicom().Clone(true)); + } + catch (OrthancException&) + { + LOG(WARNING) << "An instance was removed after the job was issued: " << instance; + return false; + } + + + /** + * Chose the target UIDs + **/ + + assert(modified->GetHasher().HashStudy() == sourceStudy_); + + std::string series = modified->GetHasher().HashSeries(); + + SeriesUidMap::const_iterator targetSeriesUid = seriesUidMap_.find(series); + + if (targetSeriesUid == seriesUidMap_.end()) + { + throw OrthancException(ErrorCode_BadFileFormat); // Should never happen + } + + + /** + * Apply user-specified modifications + **/ + + for (std::set<DicomTag>::const_iterator it = removals_.begin(); + it != removals_.end(); ++it) + { + modified->Remove(*it); + } + + for (Replacements::const_iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + modified->ReplacePlainString(it->first, it->second); + } + + + /** + * Store the new instance into Orthanc + **/ + + modified->ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, targetStudyUid_); + modified->ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, targetSeriesUid->second); + + if (targetStudy_.empty()) + { + targetStudy_ = modified->GetHasher().HashStudy(); + } + + DicomInstanceToStore toStore; + toStore.SetOrigin(origin_); + toStore.SetParsedDicomFile(*modified); + + std::string modifiedInstance; + if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + { + LOG(ERROR) << "Error while storing a modified instance " << instance; + return false; + } + + return true; + } + + + bool SplitStudyJob::HandleTrailingStep() + { + if (!keepSource_) + { + const size_t n = GetInstancesCount(); + + for (size_t i = 0; i < n; i++) + { + Json::Value tmp; + context_.DeleteResource(tmp, GetInstance(i), ResourceType_Instance); + } + } + + return true; + } + + + SplitStudyJob::SplitStudyJob(ServerContext& context, + const std::string& sourceStudy) : + context_(context), + keepSource_(false), + sourceStudy_(sourceStudy), + targetStudyUid_(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)) + { + Setup(); + + ResourceType type; + + if (!context_.GetIndex().LookupResourceType(type, sourceStudy) || + type != ResourceType_Study) + { + LOG(ERROR) << "Cannot split unknown study: " << sourceStudy; + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + void SplitStudyJob::SetOrigin(const DicomInstanceOrigin& origin) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + origin_ = origin; + } + } + + + void SplitStudyJob::SetOrigin(const RestApiCall& call) + { + SetOrigin(DicomInstanceOrigin::FromRest(call)); + } + + + void SplitStudyJob::AddSourceSeries(const std::string& series) + { + std::string parent; + + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else if (!context_.GetIndex().LookupParent(parent, series, ResourceType_Study) || + parent != sourceStudy_) + { + LOG(ERROR) << "This series does not belong to the study to be split: " << series; + throw OrthancException(ErrorCode_UnknownResource); + } + else + { + // Generate a target SeriesInstanceUID for this series + seriesUidMap_[series] = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series); + + // Add all the instances of the series as to be processed + std::list<std::string> instances; + context_.GetIndex().GetChildren(instances, series); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + AddInstance(*it); + } + } + } + + + void SplitStudyJob::SetKeepSource(bool keep) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + keepSource_ = keep; + } + + + bool SplitStudyJob::LookupTargetSeriesUid(std::string& uid, + const std::string& series) const + { + SeriesUidMap::const_iterator found = seriesUidMap_.find(series); + + if (found == seriesUidMap_.end()) + { + return false; + } + else + { + uid = found->second; + return true; + } + } + + + void SplitStudyJob::Remove(const DicomTag& tag) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + CheckAllowedTag(tag); + removals_.insert(tag); + } + + + void SplitStudyJob::Replace(const DicomTag& tag, + const std::string& value) + { + if (IsStarted()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + CheckAllowedTag(tag); + replacements_[tag] = value; + } + + + bool SplitStudyJob::LookupReplacement(std::string& value, + const DicomTag& tag) const + { + Replacements::const_iterator found = replacements_.find(tag); + + if (found == replacements_.end()) + { + return false; + } + else + { + value = found->second; + return true; + } + } + + + void SplitStudyJob::GetPublicContent(Json::Value& value) + { + SetOfInstancesJob::GetPublicContent(value); + + if (!targetStudy_.empty()) + { + value["TargetStudy"] = targetStudy_; + } + + value["TargetStudyUID"] = targetStudyUid_; + } + + + static const char* KEEP_SOURCE = "KeepSource"; + static const char* SOURCE_STUDY = "SourceStudy"; + static const char* TARGET_STUDY = "TargetStudy"; + static const char* TARGET_STUDY_UID = "TargetStudyUID"; + static const char* SERIES_UID_MAP = "SeriesUIDMap"; + static const char* ORIGIN = "Origin"; + static const char* REPLACEMENTS = "Replacements"; + static const char* REMOVALS = "Removals"; + + + SplitStudyJob::SplitStudyJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), // (*) + context_(context) + { + if (!HasTrailingStep()) + { + // Should have been set by (*) + throw OrthancException(ErrorCode_InternalError); + } + + Setup(); + + keepSource_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOURCE); + sourceStudy_ = SerializationToolbox::ReadString(serialized, SOURCE_STUDY); + targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY); + targetStudyUid_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY_UID); + SerializationToolbox::ReadMapOfStrings(seriesUidMap_, serialized, SERIES_UID_MAP); + origin_ = DicomInstanceOrigin(serialized[ORIGIN]); + SerializationToolbox::ReadMapOfTags(replacements_, serialized, REPLACEMENTS); + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); + } + + + bool SplitStudyJob::Serialize(Json::Value& target) + { + if (!SetOfInstancesJob::Serialize(target)) + { + return false; + } + else + { + target[KEEP_SOURCE] = keepSource_; + target[SOURCE_STUDY] = sourceStudy_; + target[TARGET_STUDY] = targetStudy_; + target[TARGET_STUDY_UID] = targetStudyUid_; + SerializationToolbox::WriteMapOfStrings(target, seriesUidMap_, SERIES_UID_MAP); + origin_.Serialize(target[ORIGIN]); + SerializationToolbox::WriteMapOfTags(target, replacements_, REPLACEMENTS); + SerializationToolbox::WriteSetOfTags(target, removals_, REMOVALS); + + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ServerJobs/SplitStudyJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,138 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/JobsEngine/SetOfInstancesJob.h" + +#include "../ServerContext.h" + +namespace Orthanc +{ + class SplitStudyJob : public SetOfInstancesJob + { + private: + typedef std::map<std::string, std::string> SeriesUidMap; + typedef std::map<DicomTag, std::string> Replacements; + + + ServerContext& context_; + std::set<DicomTag> allowedTags_; + bool keepSource_; + std::string sourceStudy_; + std::string targetStudy_; + std::string targetStudyUid_; + SeriesUidMap seriesUidMap_; + DicomInstanceOrigin origin_; + Replacements replacements_; + std::set<DicomTag> removals_; + + void CheckAllowedTag(const DicomTag& tag) const; + + void Setup(); + + protected: + virtual bool HandleInstance(const std::string& instance); + + virtual bool HandleTrailingStep(); + + public: + SplitStudyJob(ServerContext& context, + const std::string& sourceStudy); + + SplitStudyJob(ServerContext& context, + const Json::Value& serialized); + + const std::string& GetSourceStudy() const + { + return sourceStudy_; + } + + const std::string& GetTargetStudy() const + { + return targetStudy_; + } + + const std::string& GetTargetStudyUid() const + { + return targetStudyUid_; + } + + void AddSourceSeries(const std::string& series); + + bool IsKeepSource() const + { + return keepSource_; + } + + void SetKeepSource(bool keep); + + bool LookupTargetSeriesUid(std::string& uid, + const std::string& series) const; + + void Replace(const DicomTag& tag, + const std::string& value); + + bool LookupReplacement(std::string& value, + const DicomTag& tag) const; + + void Remove(const DicomTag& tag); + + bool IsRemoved(const DicomTag& tag) const + { + return removals_.find(tag) != removals_.end(); + } + + void SetOrigin(const DicomInstanceOrigin& origin); + + void SetOrigin(const RestApiCall& call); + + const DicomInstanceOrigin& GetOrigin() const + { + return origin_; + } + + virtual void Stop(JobStopReason reason) + { + } + + virtual void GetJobType(std::string& target) + { + target = "SplitStudy"; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& target); + }; +}
--- a/OrthancServer/ServerToolbox.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerToolbox.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,17 +38,45 @@ #include "../Core/FileStorage/StorageAccessor.h" #include "../Core/Logging.h" #include "../Core/OrthancException.h" -#include "ParsedDicomFile.h" -#include "Search/LookupIdentifierQuery.h" #include <cassert> namespace Orthanc { - namespace Toolbox + namespace ServerToolbox { + static const DicomTag patientIdentifiers[] = + { + DICOM_TAG_PATIENT_ID, + DICOM_TAG_PATIENT_NAME, + DICOM_TAG_PATIENT_BIRTH_DATE + }; + + static const DicomTag studyIdentifiers[] = + { + DICOM_TAG_PATIENT_ID, + DICOM_TAG_PATIENT_NAME, + DICOM_TAG_PATIENT_BIRTH_DATE, + DICOM_TAG_STUDY_INSTANCE_UID, + DICOM_TAG_ACCESSION_NUMBER, + DICOM_TAG_STUDY_DESCRIPTION, + DICOM_TAG_STUDY_DATE + }; + + static const DicomTag seriesIdentifiers[] = + { + DICOM_TAG_SERIES_INSTANCE_UID + }; + + static const DicomTag instanceIdentifiers[] = + { + DICOM_TAG_SOP_INSTANCE_UID + }; + + void SimplifyTags(Json::Value& target, - const Json::Value& source) + const Json::Value& source, + DicomToJsonFormat format) { assert(source.isObject()); @@ -57,9 +86,23 @@ for (size_t i = 0; i < members.size(); i++) { const Json::Value& v = source[members[i]]; - const std::string& name = v["Name"].asString(); const std::string& type = v["Type"].asString(); + std::string name; + switch (format) + { + case DicomToJsonFormat_Human: + name = v["Name"].asString(); + break; + + case DicomToJsonFormat_Short: + name = members[i]; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + if (type == "String") { target[name] = v["Value"].asString(); @@ -78,7 +121,7 @@ for (Json::Value::ArrayIndex i = 0; i < array.size(); i++) { Json::Value c; - SimplifyTags(c, array[i]); + SimplifyTags(c, array[i], format); children.append(c); } @@ -92,91 +135,9 @@ } - static std::string ValueAsString(const DicomMap& summary, - const DicomTag& tag) - { - const DicomValue& value = summary.GetValue(tag); - if (value.IsNull()) - { - return "(null)"; - } - else - { - return value.GetContent(); - } - } - - - void LogMissingRequiredTag(const DicomMap& summary) - { - std::string s, t; - - if (summary.HasTag(DICOM_TAG_PATIENT_ID)) - { - if (t.size() > 0) - t += ", "; - t += "PatientID=" + ValueAsString(summary, DICOM_TAG_PATIENT_ID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "PatientID"; - } - - if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "StudyInstanceUID=" + ValueAsString(summary, DICOM_TAG_STUDY_INSTANCE_UID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "StudyInstanceUID"; - } - - if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "SeriesInstanceUID=" + ValueAsString(summary, DICOM_TAG_SERIES_INSTANCE_UID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "SeriesInstanceUID"; - } - - if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "SOPInstanceUID=" + ValueAsString(summary, DICOM_TAG_SOP_INSTANCE_UID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "SOPInstanceUID"; - } - - if (t.size() == 0) - { - LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; - } - else - { - LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; - } - } - - - static void SetMainDicomTagsInternal(IDatabaseWrapper& database, - int64_t resource, - const DicomMap& tags) + static void StoreMainDicomTagsInternal(IDatabaseWrapper& database, + int64_t resource, + const DicomMap& tags) { DicomArray flattened(tags); @@ -194,14 +155,38 @@ } - void SetMainDicomTags(IDatabaseWrapper& database, - int64_t resource, - ResourceType level, - const DicomMap& dicomSummary) + static void StoreIdentifiers(IDatabaseWrapper& database, + int64_t resource, + ResourceType level, + const DicomMap& map) + { + const DicomTag* tags; + size_t size; + + LoadIdentifiers(tags, size, level); + + for (size_t i = 0; i < size; i++) + { + const DicomValue* value = map.TestAndGetValue(tags[i]); + if (value != NULL && + !value->IsNull() && + !value->IsBinary()) + { + std::string s = NormalizeIdentifier(value->GetContent()); + database.SetIdentifierTag(resource, tags[i], s); + } + } + } + + + void StoreMainDicomTags(IDatabaseWrapper& database, + int64_t resource, + ResourceType level, + const DicomMap& dicomSummary) { // WARNING: The database should be locked with a transaction! - LookupIdentifierQuery::StoreIdentifiers(database, resource, level, dicomSummary); + StoreIdentifiers(database, resource, level, dicomSummary); DicomMap tags; @@ -214,7 +199,7 @@ case ResourceType_Study: // Duplicate the patient tags at the study level (new in Orthanc 0.9.5 - db v6) dicomSummary.ExtractPatientInformation(tags); - SetMainDicomTagsInternal(database, resource, tags); + StoreMainDicomTagsInternal(database, resource, tags); dicomSummary.ExtractStudyInformation(tags); break; @@ -231,7 +216,7 @@ throw OrthancException(ErrorCode_InternalError); } - SetMainDicomTagsInternal(database, resource, tags); + StoreMainDicomTagsInternal(database, resource, tags); } @@ -267,6 +252,13 @@ { // WARNING: The database should be locked with a transaction! + // TODO: This function might consume much memory if level == + // ResourceType_Instance. To improve this, first download the + // list of studies, then remove the instances for each single + // study (check out OrthancRestApi::InvalidateTags for an + // example). Take this improvement into consideration for the + // next upgrade of the database schema. + const char* plural = NULL; switch (level) @@ -297,7 +289,7 @@ database.GetAllPublicIds(resources, level); for (std::list<std::string>::const_iterator - it = resources.begin(); it != resources.end(); it++) + it = resources.begin(); it != resources.end(); ++it) { // Locate the resource and one of its child instances int64_t resource, instance; @@ -307,6 +299,8 @@ tmp != level || !FindOneChildInstance(instance, database, resource, level)) { + LOG(ERROR) << "Cannot find an instance for " << EnumerationToString(level) + << " with identifier " << *it; throw OrthancException(ErrorCode_InternalError); } @@ -314,23 +308,135 @@ FileInfo attachment; if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom)) { + LOG(ERROR) << "Cannot retrieve the DICOM file associated with instance " << database.GetPublicId(instance); throw OrthancException(ErrorCode_InternalError); } - // Read and parse the content of the DICOM file - StorageAccessor accessor(storageArea); + try + { + // Read and parse the content of the DICOM file + StorageAccessor accessor(storageArea); + + std::string content; + accessor.Read(content, attachment); + + ParsedDicomFile dicom(content); + + // Update the tags of this resource + DicomMap dicomSummary; + dicom.ExtractDicomSummary(dicomSummary); + + database.ClearMainDicomTags(resource); + StoreMainDicomTags(database, resource, level, dicomSummary); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot decode the DICOM file with UUID " << attachment.GetUuid() + << " associated with instance " << database.GetPublicId(instance); + throw; + } + } + } + - std::string content; - accessor.Read(content, attachment); + void LoadIdentifiers(const DicomTag*& tags, + size_t& size, + ResourceType level) + { + switch (level) + { + case ResourceType_Patient: + tags = patientIdentifiers; + size = sizeof(patientIdentifiers) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyIdentifiers; + size = sizeof(studyIdentifiers) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesIdentifiers; + size = sizeof(seriesIdentifiers) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceIdentifiers; + size = sizeof(instanceIdentifiers) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + - ParsedDicomFile dicom(content); + std::string NormalizeIdentifier(const std::string& value) + { + std::string t; + t.reserve(value.size()); + + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == '%' || + value[i] == '_') + { + t.push_back(' '); // These characters might break wildcard queries in SQL + } + else if (isascii(value[i]) && + !iscntrl(value[i]) && + (!isspace(value[i]) || value[i] == ' ')) + { + t.push_back(value[i]); + } + } + + Toolbox::ToUpperCase(t); + + return Toolbox::StripSpaces(t); + } + + + bool IsIdentifier(const DicomTag& tag, + ResourceType level) + { + const DicomTag* tags; + size_t size; - // Update the tags of this resource - DicomMap dicomSummary; - dicom.Convert(dicomSummary); + LoadIdentifiers(tags, size, level); + + for (size_t i = 0; i < size; i++) + { + if (tag == tags[i]) + { + return true; + } + } + + return false; + } - database.ClearMainDicomTags(resource); - Toolbox::SetMainDicomTags(database, resource, level, dicomSummary); + + void ReconstructResource(ServerContext& context, + const std::string& resource) + { + LOG(WARNING) << "Reconstructing resource " << resource; + + std::list<std::string> instances; + context.GetIndex().GetChildInstances(instances, resource); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + ServerContext::DicomCacheLocker locker(context, *it); + + Json::Value dicomAsJson; + locker.GetDicom().DatasetToJson(dicomAsJson); + + std::string s = dicomAsJson.toStyledString(); + context.AddAttachment(*it, FileContentType_DicomAsJson, s.c_str(), s.size()); + + context.GetIndex().ReconstructInstance(locker.GetDicom()); } } }
--- a/OrthancServer/ServerToolbox.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/ServerToolbox.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,24 +33,22 @@ #pragma once -#include "../Core/DicomFormat/DicomMap.h" -#include "IDatabaseWrapper.h" +#include "ServerContext.h" #include <json/json.h> namespace Orthanc { - namespace Toolbox + namespace ServerToolbox { void SimplifyTags(Json::Value& target, - const Json::Value& source); - - void LogMissingRequiredTag(const DicomMap& summary); + const Json::Value& source, + DicomToJsonFormat format); - void SetMainDicomTags(IDatabaseWrapper& database, - int64_t resource, - ResourceType level, - const DicomMap& dicomSummary); + void StoreMainDicomTags(IDatabaseWrapper& database, + int64_t resource, + ResourceType level, + const DicomMap& dicomSummary); bool FindOneChildInstance(int64_t& result, IDatabaseWrapper& database, @@ -59,5 +58,17 @@ void ReconstructMainDicomTags(IDatabaseWrapper& database, IStorageArea& storageArea, ResourceType level); + + void LoadIdentifiers(const DicomTag*& tags, + size_t& size, + ResourceType level); + + bool IsIdentifier(const DicomTag& tag, + ResourceType level); + + std::string NormalizeIdentifier(const std::string& value); + + void ReconstructResource(ServerContext& context, + const std::string& resource); } }
--- a/OrthancServer/SliceOrdering.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/SliceOrdering.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,12 +95,69 @@ } + static bool IsCloseToZero(double x) + { + return fabs(x) < 10.0 * std::numeric_limits<float>::epsilon(); + } + + + bool SliceOrdering::ComputeNormal(Vector& normal, + const DicomMap& dicom) + { + std::vector<float> cosines; + + if (TokenizeVector(cosines, dicom, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6)) + { + assert(cosines.size() == 6); + normal[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; + normal[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; + normal[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; + return true; + } + else + { + return false; + } + } + + + bool SliceOrdering::IsParallelOrOpposite(const Vector& u, + const Vector& v) + { + // Check out "GeometryToolbox::IsParallelOrOpposite()" in Stone of + // Orthanc for explanations + const double u1 = u[0]; + const double u2 = u[1]; + const double u3 = u[2]; + const double normU = sqrt(u1 * u1 + u2 * u2 + u3 * u3); + + const double v1 = v[0]; + const double v2 = v[1]; + const double v3 = v[2]; + const double normV = sqrt(v1 * v1 + v2 * v2 + v3 * v3); + + if (IsCloseToZero(normU * normV)) + { + return false; + } + else + { + const double cosAngle = (u1 * v1 + u2 * v2 + u3 * v3) / (normU * normV); + + return (IsCloseToZero(cosAngle - 1.0) || // Close to +1: Parallel, non-opposite + IsCloseToZero(fabs(cosAngle) - 1.0)); // Close to -1: Parallel, opposite + } + } + + struct SliceOrdering::Instance : public boost::noncopyable { private: std::string instanceId_; bool hasPosition_; Vector position_; + bool hasNormal_; + Vector normal_; bool hasIndexInSeries_; size_t indexInSeries_; unsigned int framesCount_; @@ -140,6 +198,8 @@ position_[2] = tmp[2]; } + hasNormal_ = ComputeNormal(normal_, instance); + std::string s; hasIndexInSeries_ = false; @@ -189,6 +249,17 @@ { return framesCount_; } + + bool HasNormal() const + { + return hasNormal_; + } + + const Vector& GetNormal() const + { + assert(hasNormal_); + return normal_; + } }; @@ -225,15 +296,7 @@ throw OrthancException(ErrorCode_UnknownResource); } - std::vector<float> cosines; - hasNormal_ = TokenizeVector(cosines, series, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6); - - if (hasNormal_) - { - normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; - normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; - normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; - } + hasNormal_ = ComputeNormal(normal_, series); } @@ -267,7 +330,10 @@ for (size_t i = 0; i < instances_.size(); i++) { assert(instances_[i] != NULL); - if (!instances_[i]->HasPosition()) + + if (!instances_[i]->HasPosition() || + (instances_[i]->HasNormal() && + !IsParallelOrOpposite(instances_[i]->GetNormal(), normal_))) { return false; } @@ -276,21 +342,23 @@ PositionComparator comparator(normal_); std::sort(instances_.begin(), instances_.end(), comparator); - float a = instances_.front()->ComputeRelativePosition(normal_); - float b = instances_.back()->ComputeRelativePosition(normal_); - - if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon()) + float a = instances_[0]->ComputeRelativePosition(normal_); + for (size_t i = 1; i < instances_.size(); i++) { - // Not enough difference between the minimum and maximum - // positions along the normal of the volume - return false; + float b = instances_[i]->ComputeRelativePosition(normal_); + + if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon()) + { + // Not enough space between two slices along the normal of the volume + return false; + } + + a = b; } - else - { - // This is a 3D volume - isVolume_ = true; - return true; - } + + // This is a 3D volume + isVolume_ = true; + return true; } @@ -318,7 +386,8 @@ if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries()) { // The current "IndexInSeries" occurs 2 times: Not a proper ordering - return false; + LOG(WARNING) << "This series contains 2 slices with the same index, trying to display it anyway"; + break; } } @@ -396,6 +465,8 @@ result["Dicom"] = tmp; + Json::Value slicesShort = Json::arrayValue; + tmp.clear(); for (size_t i = 0; i < GetInstancesCount(); i++) { @@ -404,8 +475,16 @@ { tmp.append(base + "/frames/" + boost::lexical_cast<std::string>(j)); } + + Json::Value tmp2 = Json::arrayValue; + tmp2.append(GetInstanceId(i)); + tmp2.append(0); + tmp2.append(GetFramesCount(i)); + + slicesShort.append(tmp2); } result["Slices"] = tmp; + result["SlicesShort"] = slicesShort; } }
--- a/OrthancServer/SliceOrdering.h Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/SliceOrdering.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +43,7 @@ typedef float Vector[3]; struct Instance; - struct PositionComparator; + class PositionComparator; ServerIndex& index_; std::string seriesId_; @@ -51,6 +52,12 @@ std::vector<Instance*> instances_; bool isVolume_; + static bool ComputeNormal(Vector& normal, + const DicomMap& dicom); + + static bool IsParallelOrOpposite(const Vector& a, + const Vector& b); + static bool IndexInSeriesComparator(const SliceOrdering::Instance* a, const SliceOrdering::Instance* b);
--- a/OrthancServer/ToDcmtkBridge.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersServer.h" -#include "ToDcmtkBridge.h" - -#include <memory> -#include <dcmtk/dcmnet/diutil.h> - - -namespace Orthanc -{ - DcmTagKey ToDcmtkBridge::Convert(const DicomTag& tag) - { - return DcmTagKey(tag.GetGroup(), tag.GetElement()); - } - - - DcmDataset* ToDcmtkBridge::Convert(const DicomMap& map) - { - std::auto_ptr<DcmDataset> result(new DcmDataset); - - for (DicomMap::Map::const_iterator - it = map.map_.begin(); it != map.map_.end(); ++it) - { - if (!it->second->IsNull()) - { - std::string s = it->second->GetContent(); - DU_putStringDOElement(result.get(), Convert(it->first), s.c_str()); - } - } - - return result.release(); - } -}
--- a/OrthancServer/ToDcmtkBridge.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Core/DicomFormat/DicomMap.h" -#include <dcmtk/dcmdata/dcdatset.h> - -namespace Orthanc -{ - class ToDcmtkBridge - { - public: - static DcmTagKey Convert(const DicomTag& tag); - - static DcmDataset* Convert(const DicomMap& map); - }; -}
--- a/OrthancServer/main.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/OrthancServer/main.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,24 +34,21 @@ #include "PrecompiledHeadersServer.h" #include "OrthancRestApi/OrthancRestApi.h" -#include <fstream> #include <boost/algorithm/string/predicate.hpp> #include "../Core/Logging.h" -#include "../Core/Uuid.h" #include "../Core/HttpServer/EmbeddedResourceHttpHandler.h" #include "../Core/HttpServer/FilesystemHttpHandler.h" #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" -#include "DicomProtocol/DicomServer.h" -#include "DicomProtocol/ReusableDicomUserConnection.h" +#include "../Core/DicomNetworking/DicomServer.h" #include "OrthancInitialization.h" #include "ServerContext.h" #include "OrthancFindRequestHandler.h" #include "OrthancMoveRequestHandler.h" #include "ServerToolbox.h" #include "../Plugins/Engine/OrthancPlugins.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" using namespace Orthanc; @@ -77,7 +75,8 @@ if (dicomFile.size() > 0) { DicomInstanceToStore toStore; - toStore.SetDicomProtocolOrigin(remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str()); + toStore.SetOrigin(DicomInstanceOrigin::FromDicomProtocol + (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str())); toStore.SetBuffer(dicomFile); toStore.SetSummary(dicomSummary); toStore.SetJson(dicomJson); @@ -90,6 +89,23 @@ +class ModalitiesFromConfiguration : public Orthanc::DicomServer::IRemoteModalities +{ +public: + virtual bool IsSameAETitle(const std::string& aet1, + const std::string& aet2) + { + return Orthanc::Configuration::IsSameAETitle(aet1, aet2); + } + + virtual bool LookupAETitle(RemoteModalityParameters& modality, + const std::string& aet) + { + return Orthanc::Configuration::LookupDicomModalityUsingAETitle(modality, aet); + } +}; + + class MyDicomServerFactory : public IStoreRequestHandlerFactory, public IFindRequestHandlerFactory, @@ -112,8 +128,8 @@ { std::auto_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_)); - result->SetMaxResults(Configuration::GetGlobalIntegerParameter("LimitFindResults", 0)); - result->SetMaxInstances(Configuration::GetGlobalIntegerParameter("LimitFindInstances", 0)); + result->SetMaxResults(Configuration::GetGlobalUnsignedIntegerParameter("LimitFindResults", 0)); + result->SetMaxInstances(Configuration::GetGlobalUnsignedIntegerParameter("LimitFindInstances", 0)); if (result->GetMaxResults() == 0) { @@ -152,42 +168,68 @@ class OrthancApplicationEntityFilter : public IApplicationEntityFilter { private: - ServerContext& context_; + ServerContext& context_; + bool alwaysAllowEcho_; + bool alwaysAllowStore_; public: - OrthancApplicationEntityFilter(ServerContext& context) : context_(context) + OrthancApplicationEntityFilter(ServerContext& context) : + context_(context) { - } - - virtual bool IsAllowedConnection(const std::string& /*callingIp*/, - const std::string& /*callingAet*/) - { - return true; + alwaysAllowEcho_ = Configuration::GetGlobalBoolParameter("DicomAlwaysAllowEcho", true); + alwaysAllowStore_ = Configuration::GetGlobalBoolParameter("DicomAlwaysAllowStore", true); } - virtual bool IsAllowedRequest(const std::string& /*callingIp*/, - const std::string& callingAet, + virtual bool IsAllowedConnection(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + LOG(INFO) << "Incoming connection from AET " << remoteAet + << " on IP " << remoteIp << ", calling AET " << calledAet; + + return (alwaysAllowEcho_ || + alwaysAllowStore_ || + Configuration::IsKnownAETitle(remoteAet, remoteIp)); + } + + virtual bool IsAllowedRequest(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, DicomRequestType type) { - if (type == DicomRequestType_Store) + LOG(INFO) << "Incoming " << Orthanc::EnumerationToString(type) << " request from AET " + << remoteAet << " on IP " << remoteIp << ", calling AET " << calledAet; + + if (type == DicomRequestType_Echo && + alwaysAllowEcho_) { - // Incoming store requests are always accepted, even from unknown AET + // Incoming C-Echo requests are always accepted, even from unknown AET return true; } - - if (!Configuration::IsKnownAETitle(callingAet)) + else if (type == DicomRequestType_Store && + alwaysAllowStore_) { - LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << callingAet << "\""; - return false; + // Incoming C-Store requests are always accepted, even from unknown AET + return true; } else { - return true; + RemoteModalityParameters modality; + + if (Configuration::LookupDicomModalityUsingAETitle(modality, remoteAet)) + { + return modality.IsRequestAllowed(type); + } + else + { + return false; + } } } - virtual bool IsAllowedTransferSyntax(const std::string& callingIp, - const std::string& callingAet, + virtual bool IsAllowedTransferSyntax(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, TransferSyntax syntax) { std::string configuration; @@ -227,47 +269,85 @@ } { - std::string lua = "Is" + configuration; + std::string name = "Is" + configuration; - LuaScripting::Locker locker(context_.GetLua()); + LuaScripting::Lock lock(context_.GetLuaScripting()); - if (locker.GetLua().IsExistingFunction(lua.c_str())) + if (lock.GetLua().IsExistingFunction(name.c_str())) { - LuaFunctionCall call(locker.GetLua(), lua.c_str()); - call.PushString(callingAet); - call.PushString(callingIp); + LuaFunctionCall call(lock.GetLua(), name.c_str()); + call.PushString(remoteAet); + call.PushString(remoteIp); + call.PushString(calledAet); return call.ExecutePredicate(); } } return Configuration::GetGlobalBoolParameter(configuration, true); } + + + virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + static const char* configuration = "UnknownSopClassAccepted"; + + { + std::string lua = "Is" + std::string(configuration); + + LuaScripting::Lock lock(context_.GetLuaScripting()); + + if (lock.GetLua().IsExistingFunction(lua.c_str())) + { + LuaFunctionCall call(lock.GetLua(), lua.c_str()); + call.PushString(remoteAet); + call.PushString(remoteIp); + call.PushString(calledAet); + return call.ExecutePredicate(); + } + } + + return Configuration::GetGlobalBoolParameter(configuration, false); + } }; class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter { private: - ServerContext& context_; + ServerContext& context_; + OrthancPlugins* plugins_; public: - MyIncomingHttpRequestFilter(ServerContext& context) : context_(context) + MyIncomingHttpRequestFilter(ServerContext& context, + OrthancPlugins* plugins) : + context_(context), + plugins_(plugins) { } virtual bool IsAllowed(HttpMethod method, const char* uri, const char* ip, - const char* username) const + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::GetArguments& getArguments) { + if (plugins_ != NULL && + !plugins_->IsAllowed(method, uri, ip, username, httpHeaders, getArguments)) + { + return false; + } + static const char* HTTP_FILTER = "IncomingHttpRequestFilter"; - LuaScripting::Locker locker(context_.GetLua()); + LuaScripting::Lock lock(context_.GetLuaScripting()); // Test if the instance must be filtered out - if (locker.GetLua().IsExistingFunction(HTTP_FILTER)) + if (lock.GetLua().IsExistingFunction(HTTP_FILTER)) { - LuaFunctionCall call(locker.GetLua(), HTTP_FILTER); + LuaFunctionCall call(lock.GetLua(), HTTP_FILTER); switch (method) { @@ -294,6 +374,7 @@ call.PushString(uri); call.PushString(ip); call.PushString(username); + call.PushStringMap(httpHeaders); if (!call.ExecutePredicate()) { @@ -330,7 +411,7 @@ { bool isPlugin = false; -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 if (plugins_ != NULL) { plugins_->GetErrorDictionary().LogError(exception.GetErrorCode(), true); @@ -351,7 +432,7 @@ { bool isPlugin = false; -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 if (plugins_ != NULL && plugins_->GetErrorDictionary().Format(message, httpStatus, exception)) { @@ -402,7 +483,9 @@ << "Command-line options:" << std::endl << " --help\t\tdisplay this help and exit" << std::endl << " --logdir=[dir]\tdirectory where to store the log files" << std::endl - << "\t\t\t(if not used, the logs are dumped to stderr)" << std::endl + << "\t\t\t(by default, the log is dumped to stderr)" << std::endl + << " --logfile=[file]\tfile where to store the log of Orthanc" << std::endl + << "\t\t\t(by default, the log is dumped to stderr)" << std::endl << " --config=[file]\tcreate a sample configuration file and exit" << std::endl << " --errors\t\tprint the supported error codes and exit" << std::endl << " --verbose\t\tbe verbose in logs" << std::endl @@ -410,6 +493,8 @@ << " --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl << "\t\t\tdatabase (beware that the database will become" << std::endl << "\t\t\tincompatible with former versions of Orthanc)" << std::endl + << " --no-jobs\t\tDon't restart the jobs that were stored during" << std::endl + << "\t\t\tthe last execution of Orthanc" << std::endl << " --version\t\toutput version information and exit" << std::endl << std::endl << "Exit status:" << std::endl @@ -427,7 +512,8 @@ { std::cout << path << " " << ORTHANC_VERSION << std::endl - << "Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl + << "Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl + << "Copyright (C) 2017-2018 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 @@ -462,7 +548,7 @@ PrintErrorCode(ErrorCode_Plugin, "Error encountered within the plugin engine"); PrintErrorCode(ErrorCode_NotImplemented, "Not implemented yet"); PrintErrorCode(ErrorCode_ParameterOutOfRange, "Parameter out of range"); - PrintErrorCode(ErrorCode_NotEnoughMemory, "Not enough memory"); + PrintErrorCode(ErrorCode_NotEnoughMemory, "The server hosting Orthanc is running out of memory"); PrintErrorCode(ErrorCode_BadParameterType, "Bad type for a parameter"); PrintErrorCode(ErrorCode_BadSequenceOfCalls, "Bad sequence of calls"); PrintErrorCode(ErrorCode_InexistentItem, "Accessing an inexistent item"); @@ -492,6 +578,10 @@ PrintErrorCode(ErrorCode_DatabasePlugin, "The plugin implementing a custom database back-end does not fulfill the proper interface"); PrintErrorCode(ErrorCode_StorageAreaPlugin, "Error in the plugin implementing a custom storage area"); PrintErrorCode(ErrorCode_EmptyRequest, "The request is empty"); + PrintErrorCode(ErrorCode_NotAcceptable, "Cannot send a response which is acceptable according to the Accept HTTP header"); + PrintErrorCode(ErrorCode_NullPointer, "Cannot handle a NULL pointer"); + PrintErrorCode(ErrorCode_DatabaseUnavailable, "The database is currently not available (probably a transient situation)"); + PrintErrorCode(ErrorCode_CanceledJob, "This job was canceled"); PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened"); PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open"); PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database"); @@ -511,8 +601,8 @@ PrintErrorCode(ErrorCode_DirectoryOverFile, "The directory to be created is already occupied by a regular file"); PrintErrorCode(ErrorCode_FileStorageCannotWrite, "Unable to create a subdirectory or a file in the file storage"); PrintErrorCode(ErrorCode_DirectoryExpected, "The specified path does not point to a directory"); - PrintErrorCode(ErrorCode_HttpPortInUse, "The TCP port of the HTTP server is already in use"); - PrintErrorCode(ErrorCode_DicomPortInUse, "The TCP port of the DICOM server is already in use"); + PrintErrorCode(ErrorCode_HttpPortInUse, "The TCP port of the HTTP server is privileged or already in use"); + PrintErrorCode(ErrorCode_DicomPortInUse, "The TCP port of the DICOM server is privileged or already in use"); PrintErrorCode(ErrorCode_BadHttpStatusInRest, "This HTTP status is not allowed in a REST API"); PrintErrorCode(ErrorCode_RegularFileExpected, "The specified path does not point to a regular file"); PrintErrorCode(ErrorCode_PathToExecutable, "Unable to get the path to the executable"); @@ -549,6 +639,8 @@ PrintErrorCode(ErrorCode_DatabaseNotInitialized, "Plugin trying to call the database during its initialization"); PrintErrorCode(ErrorCode_SslDisabled, "Orthanc has been built without SSL support"); 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"); } std::cout << std::endl; @@ -556,26 +648,7 @@ -static void LoadLuaScripts(ServerContext& context) -{ - std::list<std::string> luaScripts; - Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); - for (std::list<std::string>::const_iterator - it = luaScripts.begin(); it != luaScripts.end(); ++it) - { - std::string path = Configuration::InterpretStringParameterAsPath(*it); - LOG(WARNING) << "Installing the Lua scripts from: " << path; - std::string script; - Toolbox::ReadFile(script, path); - - LuaScripting::Locker locker(context.GetLua()); - locker.GetLua().Execute(script); - } -} - - - -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 static void LoadPlugins(OrthancPlugins& plugins) { std::list<std::string> path; @@ -598,21 +671,52 @@ { LOG(WARNING) << "Orthanc has started"; -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 if (context.HasPlugins()) { context.GetPlugins().SignalOrthancStarted(); } #endif - context.GetLua().Execute("Initialize"); + context.GetLuaScripting().Start(); + context.GetLuaScripting().Execute("Initialize"); + + bool restart; + + for (;;) + { + ServerBarrierEvent event = SystemToolbox::ServerBarrier(restApi.LeaveBarrierFlag()); + restart = restApi.IsResetRequestReceived(); + + if (!restart && + event == ServerBarrierEvent_Reload) + { + // Handling of SIGHUP - Toolbox::ServerBarrier(restApi.LeaveBarrierFlag()); - bool restart = restApi.IsResetRequestReceived(); + if (Configuration::HasConfigurationChanged()) + { + LOG(WARNING) << "A SIGHUP signal has been received, resetting Orthanc"; + Logging::Flush(); + restart = true; + break; + } + else + { + LOG(WARNING) << "A SIGHUP signal has been received, but is ignored as the configuration has not changed"; + Logging::Flush(); + continue; + } + } + else + { + break; + } + } - context.GetLua().Execute("Finalize"); + context.GetLuaScripting().Execute("Finalize"); + context.GetLuaScripting().Stop(); -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 if (context.HasPlugins()) { context.GetPlugins().SignalOrthancStopped(); @@ -646,9 +750,9 @@ // HTTP server - MyIncomingHttpRequestFilter httpFilter(context); + MyIncomingHttpRequestFilter httpFilter(context, plugins); MongooseServer httpServer; - httpServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("HttpPort", 8042)); + httpServer.SetPortNumber(Configuration::GetGlobalUnsignedIntegerParameter("HttpPort", 8042)); httpServer.SetRemoteAccessAllowed(Configuration::GetGlobalBoolParameter("RemoteAccessAllowed", false)); httpServer.SetKeepAliveEnabled(Configuration::GetGlobalBoolParameter("KeepAlive", false)); httpServer.SetHttpCompressionEnabled(Configuration::GetGlobalBoolParameter("HttpCompressionEnabled", true)); @@ -672,8 +776,14 @@ httpServer.Register(context.GetHttpHandler()); + if (httpServer.GetPortNumber() < 1024) + { + LOG(WARNING) << "The HTTP port is privileged (" + << httpServer.GetPortNumber() << " is below 1024), " + << "make sure you run Orthanc as root/administrator"; + } + httpServer.Start(); - LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); bool restart = WaitForExit(context, restApi); @@ -695,22 +805,55 @@ } MyDicomServerFactory serverFactory(context); - - // DICOM server + OrthancApplicationEntityFilter dicomFilter(context); + ModalitiesFromConfiguration modalities; + + // Setup the DICOM server DicomServer dicomServer; - OrthancApplicationEntityFilter dicomFilter(context); + dicomServer.SetRemoteModalities(modalities); dicomServer.SetCalledApplicationEntityTitleCheck(Configuration::GetGlobalBoolParameter("DicomCheckCalledAet", false)); dicomServer.SetStoreRequestHandlerFactory(serverFactory); dicomServer.SetMoveRequestHandlerFactory(serverFactory); dicomServer.SetFindRequestHandlerFactory(serverFactory); - dicomServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("DicomPort", 4242)); + dicomServer.SetAssociationTimeout(Configuration::GetGlobalUnsignedIntegerParameter("DicomScpTimeout", 30)); + + +#if ORTHANC_ENABLE_PLUGINS == 1 + if (plugins != NULL) + { + if (plugins->HasWorklistHandler()) + { + dicomServer.SetWorklistRequestHandlerFactory(*plugins); + } + + if (plugins->HasFindHandler()) + { + dicomServer.SetFindRequestHandlerFactory(*plugins); + } + + if (plugins->HasMoveHandler()) + { + dicomServer.SetMoveRequestHandlerFactory(*plugins); + } + } +#endif + + dicomServer.SetPortNumber(Configuration::GetGlobalUnsignedIntegerParameter("DicomPort", 4242)); dicomServer.SetApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")); dicomServer.SetApplicationEntityFilter(dicomFilter); + if (dicomServer.GetPortNumber() < 1024) + { + LOG(WARNING) << "The DICOM port is privileged (" + << dicomServer.GetPortNumber() << " is below 1024), " + << "make sure you run Orthanc as root/administrator"; + } + dicomServer.Start(); - LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); + LOG(WARNING) << "DICOM server listening with AET " << dicomServer.GetApplicationEntityTitle() + << " on port: " << dicomServer.GetPortNumber(); - bool restart; + bool restart = false; ErrorCode error = ErrorCode_Success; try @@ -737,9 +880,10 @@ static bool ConfigureHttpHandler(ServerContext& context, - OrthancPlugins *plugins) + OrthancPlugins *plugins, + bool loadJobsFromDatabase) { -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 // By order of priority, first apply the "plugins" layer, so that // plugins can overwrite the built-in REST API of Orthanc if (plugins) @@ -762,65 +906,95 @@ OrthancRestApi restApi(context); context.GetHttpHandler().Register(restApi, true); - return StartDicomServer(context, restApi, plugins); + context.SetupJobsEngine(false /* not running unit tests */, loadJobsFromDatabase); + + bool restart = StartDicomServer(context, restApi, plugins); + + context.Stop(); + + return restart; } -static bool UpgradeDatabase(IDatabaseWrapper& database, - IStorageArea& storageArea, - bool allowDatabaseUpgrade) +static void UpgradeDatabase(IDatabaseWrapper& database, + IStorageArea& storageArea) { // Upgrade the schema of the database, if needed unsigned int currentVersion = database.GetDatabaseVersion(); + + LOG(WARNING) << "Starting the upgrade of the database schema"; + LOG(WARNING) << "Current database version: " << currentVersion; + LOG(WARNING) << "Database version expected by Orthanc: " << ORTHANC_DATABASE_VERSION; + if (currentVersion == ORTHANC_DATABASE_VERSION) { - return true; + LOG(WARNING) << "No upgrade is needed, start Orthanc without the \"--upgrade\" argument"; + return; } if (currentVersion > ORTHANC_DATABASE_VERSION) { LOG(ERROR) << "The version of the database schema (" << currentVersion << ") is too recent for this version of Orthanc. Please upgrade Orthanc."; - return false; - } - - if (!allowDatabaseUpgrade) - { - LOG(ERROR) << "The database schema must be upgraded from version " - << currentVersion << " to " << ORTHANC_DATABASE_VERSION - << ": Please run Orthanc with the \"--upgrade\" command-line option"; - return false; + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } LOG(WARNING) << "Upgrading the database from schema version " << currentVersion << " to " << ORTHANC_DATABASE_VERSION; - database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); + + try + { + database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); + } + catch (OrthancException&) + { + LOG(ERROR) << "Unable to run the automated upgrade, please use the replication instructions: " + << "http://book.orthanc-server.com//users/replication.html"; + throw; + } // Sanity check currentVersion = database.GetDatabaseVersion(); if (ORTHANC_DATABASE_VERSION != currentVersion) { LOG(ERROR) << "The database schema was not properly upgraded, it is still at version " << currentVersion; - throw OrthancException(ErrorCode_InternalError); + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } - - return true; + else + { + LOG(WARNING) << "The database schema was successfully upgraded, " + << "you can now start Orthanc without the \"--upgrade\" argument"; + } } static bool ConfigureServerContext(IDatabaseWrapper& database, IStorageArea& storageArea, - OrthancPlugins *plugins) + OrthancPlugins *plugins, + bool loadJobsFromDatabase) { - ServerContext context(database, storageArea); + // These configuration options must be set before creating the + // ServerContext, otherwise the possible Lua scripts will not be + // able to properly issue HTTP/HTTPS queries + HttpClient::ConfigureSsl(Configuration::GetGlobalBoolParameter("HttpsVerifyPeers", true), + Configuration::InterpretStringParameterAsPath + (Configuration::GetGlobalStringParameter("HttpsCACertificates", ""))); + HttpClient::SetDefaultVerbose(Configuration::GetGlobalBoolParameter("HttpVerbose", false)); + HttpClient::SetDefaultTimeout(Configuration::GetGlobalUnsignedIntegerParameter("HttpTimeout", 0)); + HttpClient::SetDefaultProxy(Configuration::GetGlobalStringParameter("HttpProxy", "")); - HttpClient::SetDefaultTimeout(Configuration::GetGlobalIntegerParameter("HttpTimeout", 0)); + DicomUserConnection::SetDefaultTimeout(Configuration::GetGlobalUnsignedIntegerParameter("DicomScuTimeout", 10)); + + ServerContext context(database, storageArea, false /* not running unit tests */); context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false)); context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true)); + // New option in Orthanc 1.4.2 + context.GetIndex().SetOverwriteInstances(Configuration::GetGlobalBoolParameter("OverwriteInstances", false)); + try { - context.GetIndex().SetMaximumPatientCount(Configuration::GetGlobalIntegerParameter("MaximumPatientCount", 0)); + context.GetIndex().SetMaximumPatientCount(Configuration::GetGlobalUnsignedIntegerParameter("MaximumPatientCount", 0)); } catch (...) { @@ -829,7 +1003,7 @@ try { - uint64_t size = Configuration::GetGlobalIntegerParameter("MaximumStorageSize", 0); + uint64_t size = Configuration::GetGlobalUnsignedIntegerParameter("MaximumStorageSize", 0); context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024); } catch (...) @@ -837,9 +1011,10 @@ context.GetIndex().SetMaximumStorageSize(0); } - LoadLuaScripts(context); + context.GetJobsEngine().GetRegistry().SetMaxCompletedJobs + (Configuration::GetGlobalUnsignedIntegerParameter("JobsHistorySize", 10)); -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 if (plugins) { plugins->SetServerContext(context); @@ -847,23 +1022,22 @@ } #endif - bool restart; + bool restart = false; ErrorCode error = ErrorCode_Success; try { - restart = ConfigureHttpHandler(context, plugins); + restart = ConfigureHttpHandler(context, plugins, loadJobsFromDatabase); } catch (OrthancException& e) { error = e.GetErrorCode(); } - context.Stop(); - -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 if (plugins) { + plugins->ResetServerContext(); context.ResetPlugins(); } #endif @@ -880,16 +1054,28 @@ static bool ConfigureDatabase(IDatabaseWrapper& database, IStorageArea& storageArea, OrthancPlugins *plugins, - bool allowDatabaseUpgrade) + bool upgradeDatabase, + bool loadJobsFromDatabase) { database.Open(); - - if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade)) + + unsigned int currentVersion = database.GetDatabaseVersion(); + + if (upgradeDatabase) { - return false; + UpgradeDatabase(database, storageArea); + return false; // Stop and don't restart Orthanc (cf. issue 29) + } + else if (currentVersion != ORTHANC_DATABASE_VERSION) + { + LOG(ERROR) << "The database schema must be changed from version " + << currentVersion << " to " << ORTHANC_DATABASE_VERSION + << ": Please run Orthanc with the \"--upgrade\" argument"; + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } - bool success = ConfigureServerContext(database, storageArea, plugins); + bool success = ConfigureServerContext + (database, storageArea, plugins, loadJobsFromDatabase); database.Close(); @@ -899,12 +1085,13 @@ static bool ConfigurePlugins(int argc, char* argv[], - bool allowDatabaseUpgrade) + bool upgradeDatabase, + bool loadJobsFromDatabase) { std::auto_ptr<IDatabaseWrapper> databasePtr; std::auto_ptr<IStorageArea> storage; -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 OrthancPlugins plugins; plugins.SetCommandLineArguments(argc, argv); LoadPlugins(plugins); @@ -934,26 +1121,37 @@ assert(database != NULL); assert(storage.get() != NULL); - return ConfigureDatabase(*database, *storage, &plugins, allowDatabaseUpgrade); + return ConfigureDatabase(*database, *storage, &plugins, + upgradeDatabase, loadJobsFromDatabase); -#elif ORTHANC_PLUGINS_ENABLED == 0 +#elif ORTHANC_ENABLE_PLUGINS == 0 // The plugins are disabled databasePtr.reset(Configuration::CreateDatabaseWrapper()); storage.reset(Configuration::CreateStorageArea()); - return ConfigureDatabase(*databasePtr, *storage, NULL, allowDatabaseUpgrade); + return ConfigureDatabase(*databasePtr, *storage, NULL, + upgradeDatabase, loadJobsFromDatabase); #else -# error The macro ORTHANC_PLUGINS_ENABLED must be set to 0 or 1 +# error The macro ORTHANC_ENABLE_PLUGINS must be set to 0 or 1 #endif } static bool StartOrthanc(int argc, char* argv[], - bool allowDatabaseUpgrade) + bool upgradeDatabase, + bool loadJobsFromDatabase) { - return ConfigurePlugins(argc, argv, allowDatabaseUpgrade); + return ConfigurePlugins(argc, argv, upgradeDatabase, loadJobsFromDatabase); +} + + +static bool DisplayPerformanceWarning() +{ + (void) DisplayPerformanceWarning; // Disable warning about unused function + LOG(WARNING) << "Performance warning: Non-release build, runtime debug assertions are turned on"; + return true; } @@ -961,7 +1159,8 @@ { Logging::Initialize(); - bool allowDatabaseUpgrade = false; + bool upgradeDatabase = false; + bool loadJobsFromDatabase = true; const char* configurationFile = NULL; @@ -988,6 +1187,8 @@ { // Use the first argument that does not start with a "-" as // the configuration file + + // TODO WHAT IS THE ENCODING? configurationFile = argv[i]; } } @@ -1016,6 +1217,7 @@ } else if (boost::starts_with(argument, "--logdir=")) { + // TODO WHAT IS THE ENCODING? std::string directory = argument.substr(9); try @@ -1029,12 +1231,33 @@ return -1; } } + else if (boost::starts_with(argument, "--logfile=")) + { + // TODO WHAT IS THE ENCODING? + std::string file = argument.substr(10); + + try + { + Logging::SetTargetFile(file); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot write to the specified log file (" + << file << "), aborting."; + return -1; + } + } else if (argument == "--upgrade") { - allowDatabaseUpgrade = true; + upgradeDatabase = true; + } + else if (argument == "--no-jobs") + { + loadJobsFromDatabase = false; } else if (boost::starts_with(argument, "--config=")) { + // TODO WHAT IS THE ENCODING? std::string configurationSample; GetFileResource(configurationSample, EmbeddedResources::CONFIGURATION_SAMPLE); @@ -1044,8 +1267,17 @@ #endif std::string target = argument.substr(9); - Toolbox::WriteFile(configurationSample, target); - return 0; + + try + { + SystemToolbox::WriteFile(configurationSample, target); + return 0; + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot write sample configuration as file \"" << target << "\""; + return -1; + } } else { @@ -1058,7 +1290,26 @@ * Launch Orthanc. **/ - LOG(WARNING) << "Orthanc version: " << ORTHANC_VERSION; + { + std::string version(ORTHANC_VERSION); + + if (std::string(ORTHANC_VERSION) == "mainline") + { + try + { + boost::filesystem::path exe(SystemToolbox::GetPathToExecutable()); + std::time_t creation = boost::filesystem::last_write_time(exe); + boost::posix_time::ptime converted(boost::posix_time::from_time_t(creation)); + version += " (" + boost::posix_time::to_iso_string(converted) + ")"; + } + catch (...) + { + } + } + + LOG(WARNING) << "Orthanc version: " << version; + assert(DisplayPerformanceWarning()); + } int status = 0; try @@ -1067,10 +1318,12 @@ { OrthancInitialize(configurationFile); - bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade); + bool restart = StartOrthanc(argc, argv, upgradeDatabase, loadJobsFromDatabase); if (restart) { OrthancFinalize(); + LOG(WARNING) << "Logging system is resetting"; + Logging::Reset(); } else {
--- a/Plugins/Engine/IPluginServiceProvider.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/IPluginServiceProvider.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,11 +33,11 @@ #pragma once -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 #include "../Include/orthanc/OrthancCPlugin.h" -#include "SharedLibrary.h" +#include "../../Core/SharedLibrary.h" namespace Orthanc {
--- a/Plugins/Engine/OrthancPluginDatabase.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/OrthancPluginDatabase.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ #include "../../OrthancServer/PrecompiledHeadersServer.h" #include "OrthancPluginDatabase.h" -#if ORTHANC_PLUGINS_ENABLED != 1 +#if ORTHANC_ENABLE_PLUGINS != 1 #error The plugin support is disabled #endif @@ -633,6 +634,34 @@ } + void OrthancPluginDatabase::LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) + { + if (extensions_.lookupIdentifierRange == NULL) + { + // Default implementation, for plugins using Orthanc SDK <= 1.3.2 + + LookupIdentifier(result, level, tag, IdentifierConstraintType_GreaterOrEqual, start); + + std::list<int64_t> b; + LookupIdentifier(result, level, tag, IdentifierConstraintType_SmallerOrEqual, end); + + result.splice(result.end(), b); + } + else + { + ResetAnswers(); + CheckSuccess(extensions_.lookupIdentifierRange(GetContext(), payload_, Plugins::Convert(level), + tag.GetGroup(), tag.GetElement(), + start.c_str(), end.c_str())); + ForwardAnswers(result); + } + } + + bool OrthancPluginDatabase::LookupMetadata(std::string& target, int64_t id, MetadataType type) @@ -986,7 +1015,7 @@ { const OrthancPluginDicomTag& tag = *reinterpret_cast<const OrthancPluginDicomTag*>(answer.valueGeneric); assert(answerDicomMap_ != NULL); - answerDicomMap_->SetValue(tag.group, tag.element, std::string(tag.value)); + answerDicomMap_->SetValue(tag.group, tag.element, std::string(tag.value), false); break; }
--- a/Plugins/Engine/OrthancPluginDatabase.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/OrthancPluginDatabase.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,12 +33,12 @@ #pragma once -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 +#include "../../Core/SharedLibrary.h" #include "../../OrthancServer/IDatabaseWrapper.h" #include "../Include/orthanc/OrthancCDatabasePlugin.h" #include "PluginsErrorDictionary.h" -#include "SharedLibrary.h" namespace Orthanc { @@ -212,6 +213,12 @@ IdentifierConstraintType type, const std::string& value); + virtual void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end); + virtual bool LookupMetadata(std::string& target, int64_t id, MetadataType type);
--- a/Plugins/Engine/OrthancPlugins.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,20 +34,25 @@ #include "../../OrthancServer/PrecompiledHeadersServer.h" #include "OrthancPlugins.h" -#if ORTHANC_PLUGINS_ENABLED != 1 +#if ORTHANC_ENABLE_PLUGINS != 1 #error The plugin support is disabled #endif #include "../../Core/ChunkedBuffer.h" +#include "../../Core/DicomFormat/DicomArray.h" #include "../../Core/HttpServer/HttpToolbox.h" #include "../../Core/Logging.h" #include "../../Core/OrthancException.h" +#include "../../Core/SerializationToolbox.h" #include "../../Core/Toolbox.h" -#include "../../OrthancServer/FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/DicomParsing/ToDcmtkBridge.h" #include "../../OrthancServer/OrthancInitialization.h" #include "../../OrthancServer/ServerContext.h" #include "../../OrthancServer/ServerToolbox.h" +#include "../../OrthancServer/Search/HierarchicalMatcher.h" +#include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" #include "../../Core/Compression/ZlibCompressor.h" #include "../../Core/Compression/GzipCompressor.h" #include "../../Core/Images/Image.h" @@ -55,12 +61,78 @@ #include "../../Core/Images/JpegReader.h" #include "../../Core/Images/JpegWriter.h" #include "../../Core/Images/ImageProcessing.h" +#include "../../OrthancServer/DefaultDicomImageDecoder.h" +#include "../../OrthancServer/OrthancFindRequestHandler.h" #include "PluginsEnumerations.h" +#include "PluginsJob.h" #include <boost/regex.hpp> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcdicent.h> namespace Orthanc { + static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, + const void* data, + size_t size) + { + target.size = size; + + if (size == 0) + { + target.data = NULL; + } + else + { + target.data = malloc(size); + if (target.data != NULL) + { + memcpy(target.data, data, size); + } + else + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + } + + + static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, + const std::string& str) + { + if (str.size() == 0) + { + target.size = 0; + target.data = NULL; + } + else + { + CopyToMemoryBuffer(target, str.c_str(), str.size()); + } + } + + + static char* CopyString(const std::string& str) + { + char *result = reinterpret_cast<char*>(malloc(str.size() + 1)); + if (result == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + if (str.size() == 0) + { + result[0] = '\0'; + } + else + { + memcpy(result, &str[0], str.size() + 1); + } + + return result; + } + + namespace { class PluginStorageArea : public IStorageArea @@ -179,11 +251,72 @@ return new PluginStorageArea(callbacks_, errorDictionary_); } }; + + + class OrthancPeers : public boost::noncopyable + { + private: + std::vector<std::string> names_; + std::vector<WebServiceParameters> parameters_; + + void CheckIndex(size_t i) const + { + assert(names_.size() == parameters_.size()); + if (i >= names_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + public: + OrthancPeers() + { + std::set<std::string> peers; + Configuration::GetListOfOrthancPeers(peers); + + names_.reserve(peers.size()); + parameters_.reserve(peers.size()); + + for (std::set<std::string>::const_iterator + it = peers.begin(); it != peers.end(); ++it) + { + WebServiceParameters peer; + if (Configuration::GetOrthancPeer(peer, *it)) + { + names_.push_back(*it); + parameters_.push_back(peer); + } + } + } + + size_t GetPeersCount() const + { + return names_.size(); + } + + const std::string& GetPeerName(size_t i) const + { + CheckIndex(i); + return names_[i]; + } + + const WebServiceParameters& GetPeerParameters(size_t i) const + { + CheckIndex(i); + return parameters_[i]; + } + }; } - struct OrthancPlugins::PImpl + class OrthancPlugins::PImpl { + private: + boost::mutex contextMutex_; + ServerContext* context_; + + + public: class RestCallback : public boost::noncopyable { private: @@ -233,22 +366,71 @@ }; + class ServerContextLock + { + private: + boost::mutex::scoped_lock lock_; + ServerContext* context_; + + public: + ServerContextLock(PImpl& that) : + lock_(that.contextMutex_), + context_(that.context_) + { + if (context_ == NULL) + { + throw OrthancException(ErrorCode_DatabaseNotInitialized); + } + } + + ServerContext& GetContext() + { + assert(context_ != NULL); + return *context_; + } + }; + + + void SetServerContext(ServerContext* context) + { + boost::mutex::scoped_lock lock(contextMutex_); + context_ = context; + } + + typedef std::pair<std::string, _OrthancPluginProperty> Property; typedef std::list<RestCallback*> RestCallbacks; typedef std::list<OrthancPluginOnStoredInstanceCallback> OnStoredCallbacks; typedef std::list<OrthancPluginOnChangeCallback> OnChangeCallbacks; + typedef std::list<OrthancPluginIncomingHttpRequestFilter> IncomingHttpRequestFilters; + typedef std::list<OrthancPluginIncomingHttpRequestFilter2> IncomingHttpRequestFilters2; + typedef std::list<OrthancPluginDecodeImageCallback> DecodeImageCallbacks; + typedef std::list<OrthancPluginJobsUnserializer> JobsUnserializers; typedef std::map<Property, std::string> Properties; PluginsManager manager_; - ServerContext* context_; + RestCallbacks restCallbacks_; OnStoredCallbacks onStoredCallbacks_; OnChangeCallbacks onChangeCallbacks_; + OrthancPluginFindCallback findCallback_; + OrthancPluginWorklistCallback worklistCallback_; + DecodeImageCallbacks decodeImageCallbacks_; + JobsUnserializers jobsUnserializers_; + _OrthancPluginMoveCallback moveCallbacks_; + IncomingHttpRequestFilters incomingHttpRequestFilters_; + IncomingHttpRequestFilters2 incomingHttpRequestFilters2_; std::auto_ptr<StorageAreaFactory> storageArea_; + boost::recursive_mutex restCallbackMutex_; boost::recursive_mutex storedCallbackMutex_; boost::recursive_mutex changeCallbackMutex_; + boost::mutex findCallbackMutex_; + boost::mutex worklistCallbackMutex_; + boost::mutex decodeImageCallbackMutex_; + boost::mutex jobsUnserializersMutex_; boost::recursive_mutex invokeServiceMutex_; + Properties properties_; int argc_; char** argv_; @@ -257,37 +439,396 @@ PImpl() : context_(NULL), + findCallback_(NULL), + worklistCallback_(NULL), argc_(1), argv_(NULL) { + memset(&moveCallbacks_, 0, sizeof(moveCallbacks_)); } }; - static char* CopyString(const std::string& str) + class OrthancPlugins::WorklistHandler : public IWorklistRequestHandler { - char *result = reinterpret_cast<char*>(malloc(str.size() + 1)); - if (result == NULL) + private: + OrthancPlugins& that_; + std::auto_ptr<HierarchicalMatcher> matcher_; + std::auto_ptr<ParsedDicomFile> filtered_; + ParsedDicomFile* currentQuery_; + + void Reset() + { + matcher_.reset(); + filtered_.reset(); + currentQuery_ = NULL; + } + + public: + WorklistHandler(OrthancPlugins& that) : that_(that) + { + Reset(); + } + + virtual void Handle(DicomFindAnswers& answers, + ParsedDicomFile& query, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) + { + static const char* LUA_CALLBACK = "IncomingWorklistRequestFilter"; + + { + PImpl::ServerContextLock lock(*that_.pimpl_); + LuaScripting::Lock lua(lock.GetContext().GetLuaScripting()); + + if (!lua.GetLua().IsExistingFunction(LUA_CALLBACK)) + { + currentQuery_ = &query; + } + else + { + Json::Value source, origin; + query.DatasetToJson(source, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); + + OrthancFindRequestHandler::FormatOrigin + (origin, remoteIp, remoteAet, calledAet, manufacturer); + + LuaFunctionCall call(lua.GetLua(), LUA_CALLBACK); + call.PushJson(source); + call.PushJson(origin); + + Json::Value target; + call.ExecuteToJson(target, true); + + filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None)); + currentQuery_ = filtered_.get(); + } + } + + matcher_.reset(new HierarchicalMatcher(*currentQuery_)); + + { + boost::mutex::scoped_lock lock(that_.pimpl_->worklistCallbackMutex_); + + if (that_.pimpl_->worklistCallback_) + { + OrthancPluginErrorCode error = that_.pimpl_->worklistCallback_ + (reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers), + reinterpret_cast<const OrthancPluginWorklistQuery*>(this), + remoteAet.c_str(), + calledAet.c_str()); + + if (error != OrthancPluginErrorCode_Success) + { + Reset(); + that_.GetErrorDictionary().LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } + } + + Reset(); + } + } + + void GetDicomQuery(OrthancPluginMemoryBuffer& target) const { - throw OrthancException(ErrorCode_NotEnoughMemory); + if (currentQuery_ == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + + std::string dicom; + currentQuery_->SaveToMemoryBuffer(dicom); + CopyToMemoryBuffer(target, dicom.c_str(), dicom.size()); + } + + bool IsMatch(const void* dicom, + size_t size) const + { + if (matcher_.get() == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + + ParsedDicomFile f(dicom, size); + return matcher_->Match(f); + } + + void AddAnswer(OrthancPluginWorklistAnswers* answers, + const void* dicom, + size_t size) const + { + if (matcher_.get() == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + + ParsedDicomFile f(dicom, size); + std::auto_ptr<ParsedDicomFile> summary(matcher_->Extract(f)); + reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary); + } + }; + + + class OrthancPlugins::FindHandler : public IFindRequestHandler + { + private: + OrthancPlugins& that_; + std::auto_ptr<DicomArray> currentQuery_; + + void Reset() + { + currentQuery_.reset(NULL); } - if (str.size() == 0) + public: + FindHandler(OrthancPlugins& that) : that_(that) + { + Reset(); + } + + virtual void Handle(DicomFindAnswers& answers, + const DicomMap& input, + const std::list<DicomTag>& sequencesToReturn, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) + { + DicomMap tmp; + tmp.Assign(input); + + for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin(); + it != sequencesToReturn.end(); ++it) + { + if (!input.HasTag(*it)) + { + tmp.SetValue(*it, "", false); + } + } + + { + boost::mutex::scoped_lock lock(that_.pimpl_->findCallbackMutex_); + currentQuery_.reset(new DicomArray(tmp)); + + if (that_.pimpl_->findCallback_) + { + OrthancPluginErrorCode error = that_.pimpl_->findCallback_ + (reinterpret_cast<OrthancPluginFindAnswers*>(&answers), + reinterpret_cast<const OrthancPluginFindQuery*>(this), + remoteAet.c_str(), + calledAet.c_str()); + + if (error != OrthancPluginErrorCode_Success) + { + Reset(); + that_.GetErrorDictionary().LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } + } + + Reset(); + } + } + + void Invoke(_OrthancPluginService service, + const _OrthancPluginFindOperation& operation) const { - result[0] = '\0'; + if (currentQuery_.get() == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + + switch (service) + { + case _OrthancPluginService_GetFindQuerySize: + *operation.resultUint32 = currentQuery_->GetSize(); + break; + + case _OrthancPluginService_GetFindQueryTag: + { + const DicomTag& tag = currentQuery_->GetElement(operation.index).GetTag(); + *operation.resultGroup = tag.GetGroup(); + *operation.resultElement = tag.GetElement(); + break; + } + + case _OrthancPluginService_GetFindQueryTagName: + { + const DicomElement& element = currentQuery_->GetElement(operation.index); + *operation.resultString = CopyString(FromDcmtkBridge::GetTagName(element)); + break; + } + + case _OrthancPluginService_GetFindQueryValue: + { + *operation.resultString = CopyString(currentQuery_->GetElement(operation.index).GetValue().GetContent()); + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } } - else + }; + + + + class OrthancPlugins::MoveHandler : public IMoveRequestHandler + { + private: + class Driver : public IMoveRequestIterator { - memcpy(result, &str[0], str.size() + 1); + private: + void* driver_; + unsigned int count_; + unsigned int pos_; + OrthancPluginApplyMove apply_; + OrthancPluginFreeMove free_; + + public: + Driver(void* driver, + unsigned int count, + OrthancPluginApplyMove apply, + OrthancPluginFreeMove free) : + driver_(driver), + count_(count), + pos_(0), + apply_(apply), + free_(free) + { + if (driver_ == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + } + + virtual ~Driver() + { + if (driver_ != NULL) + { + free_(driver_); + driver_ = NULL; + } + } + + virtual unsigned int GetSubOperationCount() const + { + return count_; + } + + virtual Status DoNext() + { + if (pos_ >= count_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + OrthancPluginErrorCode error = apply_(driver_); + if (error != OrthancPluginErrorCode_Success) + { + LOG(ERROR) << "Error while doing C-Move from plugin: " << EnumerationToString(static_cast<ErrorCode>(error)); + return Status_Failure; + } + else + { + pos_++; + return Status_Success; + } + } + } + }; + + + _OrthancPluginMoveCallback params_; + + + static std::string ReadTag(const DicomMap& input, + const DicomTag& tag) + { + const DicomValue* value = input.TestAndGetValue(tag); + if (value != NULL && + !value->IsBinary() && + !value->IsNull()) + { + return value->GetContent(); + } + else + { + return std::string(); + } } - - return result; - } + + + + public: + MoveHandler(OrthancPlugins& that) + { + boost::recursive_mutex::scoped_lock lock(that.pimpl_->invokeServiceMutex_); + params_ = that.pimpl_->moveCallbacks_; + + if (params_.callback == NULL || + params_.getMoveSize == NULL || + params_.applyMove == NULL || + params_.freeMove == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + } + + virtual IMoveRequestIterator* Handle(const std::string& targetAet, + const DicomMap& input, + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet, + uint16_t originatorId) + { + std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL); + std::string patientId = ReadTag(input, DICOM_TAG_PATIENT_ID); + std::string accessionNumber = ReadTag(input, DICOM_TAG_ACCESSION_NUMBER); + std::string studyInstanceUid = ReadTag(input, DICOM_TAG_STUDY_INSTANCE_UID); + std::string seriesInstanceUid = ReadTag(input, DICOM_TAG_SERIES_INSTANCE_UID); + std::string sopInstanceUid = ReadTag(input, DICOM_TAG_SOP_INSTANCE_UID); + + OrthancPluginResourceType level = OrthancPluginResourceType_None; + + if (!levelString.empty()) + { + level = Plugins::Convert(StringToResourceType(levelString.c_str())); + } + + void* driver = params_.callback(level, + patientId.empty() ? NULL : patientId.c_str(), + accessionNumber.empty() ? NULL : accessionNumber.c_str(), + studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(), + seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(), + sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(), + originatorAet.c_str(), + calledAet.c_str(), + targetAet.c_str(), + originatorId); + + if (driver == NULL) + { + LOG(ERROR) << "Plugin cannot create a driver for an incoming C-MOVE request"; + throw OrthancException(ErrorCode_Plugin); + } + + unsigned int size = params_.getMoveSize(driver); + + return new Driver(driver, size, params_.applyMove, params_.freeMove); + } + }; + OrthancPlugins::OrthancPlugins() { + /* Sanity check of the compiler */ if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || sizeof(int32_t) != sizeof(_OrthancPluginService) || @@ -301,16 +842,21 @@ sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType) || sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) || + sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) || 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) || static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast<int>(DicomToJsonFlags_IncludePixelData) || static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToNull) || - static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii)) + static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii) || + static_cast<int>(OrthancPluginCreateDicomFlags_DecodeDataUriScheme) != static_cast<int>(DicomFromJsonFlags_DecodeDataUriScheme) || + static_cast<int>(OrthancPluginCreateDicomFlags_GenerateIdentifiers) != static_cast<int>(DicomFromJsonFlags_GenerateIdentifiers)) + { - /* Sanity check of the compiler */ throw OrthancException(ErrorCode_Plugin); } @@ -321,10 +867,15 @@ void OrthancPlugins::SetServerContext(ServerContext& context) { - pimpl_->context_ = &context; + pimpl_->SetServerContext(&context); } + void OrthancPlugins::ResetServerContext() + { + pimpl_->SetServerContext(NULL); + } + OrthancPlugins::~OrthancPlugins() { @@ -485,7 +1036,7 @@ } else { - GetErrorDictionary().LogError(error, true); + GetErrorDictionary().LogError(error, false); throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -546,46 +1097,6 @@ - static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, - const void* data, - size_t size) - { - target.size = size; - - if (size == 0) - { - target.data = NULL; - } - else - { - target.data = malloc(size); - if (target.data != NULL) - { - memcpy(target.data, data, size); - } - else - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - } - - - static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, - const std::string& str) - { - if (str.size() == 0) - { - target.size = 0; - target.data = NULL; - } - else - { - CopyToMemoryBuffer(target, str.c_str(), str.size()); - } - } - - void OrthancPlugins::RegisterRestCallback(const void* parameters, bool lock) { @@ -593,7 +1104,7 @@ *reinterpret_cast<const _OrthancPluginRestCallback*>(parameters); LOG(INFO) << "Plugin has registered a REST callback " - << (lock ? "with" : "witout") + << (lock ? "with" : "without") << " mutual exclusion on: " << p.pathRegularExpression; @@ -622,6 +1133,111 @@ } + void OrthancPlugins::RegisterWorklistCallback(const void* parameters) + { + const _OrthancPluginWorklistCallback& p = + *reinterpret_cast<const _OrthancPluginWorklistCallback*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_); + + if (pimpl_->worklistCallback_ != NULL) + { + LOG(ERROR) << "Can only register one plugin to handle modality worklists"; + throw OrthancException(ErrorCode_Plugin); + } + else + { + LOG(INFO) << "Plugin has registered a callback to handle modality worklists"; + pimpl_->worklistCallback_ = p.callback; + } + } + + + void OrthancPlugins::RegisterFindCallback(const void* parameters) + { + const _OrthancPluginFindCallback& p = + *reinterpret_cast<const _OrthancPluginFindCallback*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_); + + if (pimpl_->findCallback_ != NULL) + { + LOG(ERROR) << "Can only register one plugin to handle C-FIND requests"; + throw OrthancException(ErrorCode_Plugin); + } + else + { + LOG(INFO) << "Plugin has registered a callback to handle C-FIND requests"; + pimpl_->findCallback_ = p.callback; + } + } + + + void OrthancPlugins::RegisterMoveCallback(const void* parameters) + { + // invokeServiceMutex_ is assumed to be locked + + const _OrthancPluginMoveCallback& p = + *reinterpret_cast<const _OrthancPluginMoveCallback*>(parameters); + + if (pimpl_->moveCallbacks_.callback != NULL) + { + LOG(ERROR) << "Can only register one plugin to handle C-MOVE requests"; + throw OrthancException(ErrorCode_Plugin); + } + else + { + LOG(INFO) << "Plugin has registered a callback to handle C-MOVE requests"; + pimpl_->moveCallbacks_ = p; + } + } + + + void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters) + { + const _OrthancPluginDecodeImageCallback& p = + *reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_); + + pimpl_->decodeImageCallbacks_.push_back(p.callback); + LOG(INFO) << "Plugin has registered a callback to decode DICOM images (" + << pimpl_->decodeImageCallbacks_.size() << " decoder(s) now active)"; + } + + + void OrthancPlugins::RegisterJobsUnserializer(const void* parameters) + { + const _OrthancPluginJobsUnserializer& p = + *reinterpret_cast<const _OrthancPluginJobsUnserializer*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_); + + pimpl_->jobsUnserializers_.push_back(p.unserializer); + LOG(INFO) << "Plugin has registered a callback to unserialize jobs (" + << pimpl_->jobsUnserializers_.size() << " unserializer(s) now active)"; + } + + + void OrthancPlugins::RegisterIncomingHttpRequestFilter(const void* parameters) + { + const _OrthancPluginIncomingHttpRequestFilter& p = + *reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter*>(parameters); + + LOG(INFO) << "Plugin has registered a callback to filter incoming HTTP requests"; + pimpl_->incomingHttpRequestFilters_.push_back(p.callback); + } + + + void OrthancPlugins::RegisterIncomingHttpRequestFilter2(const void* parameters) + { + const _OrthancPluginIncomingHttpRequestFilter2& p = + *reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter2*>(parameters); + + LOG(INFO) << "Plugin has registered a callback to filter incoming HTTP requests"; + pimpl_->incomingHttpRequestFilters2_.push_back(p.callback); + } + void OrthancPlugins::AnswerBuffer(const void* parameters) { @@ -772,15 +1388,6 @@ } - void OrthancPlugins::CheckContextAvailable() - { - if (!pimpl_->context_) - { - throw OrthancException(ErrorCode_DatabaseNotInitialized); - } - } - - void OrthancPlugins::GetDicomForInstance(const void* parameters) { const _OrthancPluginGetDicomForInstance& p = @@ -788,8 +1395,10 @@ std::string dicom; - CheckContextAvailable(); - pimpl_->context_->ReadFile(dicom, p.instanceId, FileContentType_Dicom); + { + PImpl::ServerContextLock lock(*pimpl_); + lock.GetContext().ReadDicom(dicom, p.instanceId); + } CopyToMemoryBuffer(*p.target, dicom); } @@ -804,17 +1413,57 @@ LOG(INFO) << "Plugin making REST GET call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)"); - CheckContextAvailable(); - IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); + IHttpHandler* handler; + + { + PImpl::ServerContextLock lock(*pimpl_); + handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); + } std::string result; - if (HttpToolbox::SimpleGet(result, handler, RequestOrigin_Plugins, p.uri)) + if (HttpToolbox::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri)) { CopyToMemoryBuffer(*p.target, result); } else { - throw OrthancException(ErrorCode_BadRequest); + throw OrthancException(ErrorCode_UnknownResource); + } + } + + + void OrthancPlugins::RestApiGet2(const void* parameters) + { + const _OrthancPluginRestApiGet2& p = + *reinterpret_cast<const _OrthancPluginRestApiGet2*>(parameters); + + LOG(INFO) << "Plugin making REST GET call on URI " << p.uri + << (p.afterPlugins ? " (after plugins)" : " (built-in API)"); + + IHttpHandler::Arguments headers; + + for (uint32_t i = 0; i < p.headersCount; i++) + { + std::string name(p.headersKeys[i]); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + headers[name] = p.headersValues[i]; + } + + IHttpHandler* handler; + + { + PImpl::ServerContextLock lock(*pimpl_); + handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins); + } + + std::string result; + if (HttpToolbox::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri, headers)) + { + CopyToMemoryBuffer(*p.target, result); + } + else + { + throw OrthancException(ErrorCode_UnknownResource); } } @@ -829,19 +1478,23 @@ LOG(INFO) << "Plugin making REST " << EnumerationToString(isPost ? HttpMethod_Post : HttpMethod_Put) << " call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)"); - CheckContextAvailable(); - IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); - + IHttpHandler* handler; + + { + PImpl::ServerContextLock lock(*pimpl_); + handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); + } + std::string result; if (isPost ? - HttpToolbox::SimplePost(result, handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize) : - HttpToolbox::SimplePut (result, handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize)) + HttpToolbox::SimplePost(result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize) : + HttpToolbox::SimplePut (result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize)) { CopyToMemoryBuffer(*p.target, result); } else { - throw OrthancException(ErrorCode_BadRequest); + throw OrthancException(ErrorCode_UnknownResource); } } @@ -853,12 +1506,16 @@ LOG(INFO) << "Plugin making REST DELETE call on URI " << uri << (afterPlugins ? " (after plugins)" : " (built-in API)"); - CheckContextAvailable(); - IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); - - if (!HttpToolbox::SimpleDelete(handler, RequestOrigin_Plugins, uri)) + IHttpHandler* handler; + { - throw OrthancException(ErrorCode_BadRequest); + PImpl::ServerContextLock lock(*pimpl_); + handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); + } + + if (!HttpToolbox::SimpleDelete(*handler, RequestOrigin_Plugins, uri)) + { + throw OrthancException(ErrorCode_UnknownResource); } } @@ -909,10 +1566,12 @@ throw OrthancException(ErrorCode_InternalError); } - CheckContextAvailable(); - std::list<std::string> result; - pimpl_->context_->GetIndex().LookupIdentifierExact(result, level, tag, p.argument); + + { + PImpl::ServerContextLock lock(*pimpl_); + lock.GetContext().GetIndex().LookupIdentifierExact(result, level, tag, p.argument); + } if (result.size() == 1) { @@ -991,7 +1650,7 @@ switch (service) { case _OrthancPluginService_GetInstanceRemoteAet: - *p.resultString = instance.GetRemoteAet(); + *p.resultString = instance.GetOrigin().GetRemoteAetC(); return; case _OrthancPluginService_GetInstanceSize: @@ -1023,7 +1682,7 @@ else { Json::Value simplified; - Toolbox::SimplifyTags(simplified, instance.GetJson()); + ServerToolbox::SimplifyTags(simplified, instance.GetJson(), DicomToJsonFormat_Human); s = writer.write(simplified); } @@ -1031,6 +1690,10 @@ return; } + case _OrthancPluginService_GetInstanceOrigin: // New in Orthanc 0.9.5 + *p.resultOrigin = Plugins::Convert(instance.GetOrigin().GetRequestOrigin()); + return; + default: throw OrthancException(ErrorCode_InternalError); } @@ -1095,6 +1758,25 @@ } + static OrthancPluginImage* ReturnImage(std::auto_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)); + ImageProcessing::Copy(*copy, *image); + image.reset(NULL); + return reinterpret_cast<OrthancPluginImage*>(copy.release()); + } + else + { + return reinterpret_cast<OrthancPluginImage*>(image.release()); + } + } + + void OrthancPlugins::UncompressImage(const void* parameters) { const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters); @@ -1117,11 +1799,17 @@ break; } + case OrthancPluginImageFormat_Dicom: + { + image.reset(Decode(p.data, p.size, 0)); + break; + } + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } - *(p.target) = reinterpret_cast<OrthancPluginImage*>(image.release()); + *(p.target) = ReturnImage(image); } @@ -1131,12 +1819,15 @@ std::string compressed; + ImageAccessor accessor; + accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer); + switch (p.imageFormat) { case OrthancPluginImageFormat_Png: { PngWriter writer; - writer.WriteToMemory(compressed, p.width, p.height, p.pitch, Plugins::Convert(p.pixelFormat), p.buffer); + writer.WriteToMemory(compressed, accessor); break; } @@ -1144,7 +1835,7 @@ { JpegWriter writer; writer.SetQuality(p.quality); - writer.WriteToMemory(compressed, p.width, p.height, p.pitch, Plugins::Convert(p.pixelFormat), p.buffer); + writer.WriteToMemory(compressed, accessor); break; } @@ -1203,15 +1894,209 @@ } + void OrthancPlugins::CallHttpClient2(const void* parameters) + { + const _OrthancPluginCallHttpClient2& p = *reinterpret_cast<const _OrthancPluginCallHttpClient2*>(parameters); + + HttpClient client; + client.SetUrl(p.url); + client.SetConvertHeadersToLowerCase(false); + + if (p.timeout != 0) + { + client.SetTimeout(p.timeout); + } + + if (p.username != NULL && + p.password != NULL) + { + client.SetCredentials(p.username, p.password); + } + + if (p.certificateFile != NULL) + { + std::string certificate(p.certificateFile); + std::string key, password; + + if (p.certificateKeyFile) + { + key.assign(p.certificateKeyFile); + } + + if (p.certificateKeyPassword) + { + password.assign(p.certificateKeyPassword); + } + + client.SetClientCertificate(certificate, key, password); + } + + client.SetPkcs11Enabled(p.pkcs11 ? true : false); + + for (uint32_t i = 0; i < p.headersCount; i++) + { + if (p.headersKeys[i] == NULL || + p.headersValues[i] == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + client.AddHeader(p.headersKeys[i], p.headersValues[i]); + } + + switch (p.method) + { + case OrthancPluginHttpMethod_Get: + client.SetMethod(HttpMethod_Get); + break; + + case OrthancPluginHttpMethod_Post: + client.SetMethod(HttpMethod_Post); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Put: + client.SetMethod(HttpMethod_Put); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Delete: + client.SetMethod(HttpMethod_Delete); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::string body; + HttpClient::HttpHeaders headers; + + bool success = client.Apply(body, headers); + + // The HTTP request has succeeded + *p.httpStatus = static_cast<uint16_t>(client.GetLastStatus()); + + if (!success) + { + HttpClient::ThrowException(client.GetLastStatus()); + } + + // Copy the HTTP headers of the answer, if the plugin requested them + if (p.answerHeaders != NULL) + { + Json::Value json = Json::objectValue; + + for (HttpClient::HttpHeaders::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + json[it->first] = it->second; + } + + std::string s = json.toStyledString(); + CopyToMemoryBuffer(*p.answerHeaders, s); + } + + // Copy the body of the answer if it makes sense + if (p.method != OrthancPluginHttpMethod_Delete) + { + CopyToMemoryBuffer(*p.answerBody, body); + } + } + + + void OrthancPlugins::CallPeerApi(const void* parameters) + { + const _OrthancPluginCallPeerApi& p = *reinterpret_cast<const _OrthancPluginCallPeerApi*>(parameters); + const OrthancPeers& peers = *reinterpret_cast<const OrthancPeers*>(p.peers); + + HttpClient client(peers.GetPeerParameters(p.peerIndex), p.uri); + client.SetConvertHeadersToLowerCase(false); + + if (p.timeout != 0) + { + client.SetTimeout(p.timeout); + } + + for (uint32_t i = 0; i < p.additionalHeadersCount; i++) + { + if (p.additionalHeadersKeys[i] == NULL || + p.additionalHeadersValues[i] == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + client.AddHeader(p.additionalHeadersKeys[i], p.additionalHeadersValues[i]); + } + + switch (p.method) + { + case OrthancPluginHttpMethod_Get: + client.SetMethod(HttpMethod_Get); + break; + + case OrthancPluginHttpMethod_Post: + client.SetMethod(HttpMethod_Post); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Put: + client.SetMethod(HttpMethod_Put); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Delete: + client.SetMethod(HttpMethod_Delete); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::string body; + HttpClient::HttpHeaders headers; + + bool success = client.Apply(body, headers); + + // The HTTP request has succeeded + *p.httpStatus = static_cast<uint16_t>(client.GetLastStatus()); + + if (!success) + { + HttpClient::ThrowException(client.GetLastStatus()); + } + + // Copy the HTTP headers of the answer, if the plugin requested them + if (p.answerHeaders != NULL) + { + Json::Value json = Json::objectValue; + + for (HttpClient::HttpHeaders::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + json[it->first] = it->second; + } + + std::string s = json.toStyledString(); + CopyToMemoryBuffer(*p.answerHeaders, s); + } + + // Copy the body of the answer if it makes sense + if (p.method != OrthancPluginHttpMethod_Delete) + { + CopyToMemoryBuffer(*p.answerBody, body); + } + } + + void OrthancPlugins::ConvertPixelFormat(const void* parameters) { 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())); + std::auto_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false)); ImageProcessing::Convert(*target, source); - *(p.target) = reinterpret_cast<OrthancPluginImage*>(target.release()); + *(p.target) = ReturnImage(target); } @@ -1264,43 +2149,243 @@ { if (p.instanceId == NULL) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_NullPointer); } std::string content; - pimpl_->context_->ReadFile(content, p.instanceId, FileContentType_Dicom); + + { + PImpl::ServerContextLock lock(*pimpl_); + lock.GetContext().ReadDicom(content, p.instanceId); + } + dicom.reset(new ParsedDicomFile(content)); } Json::Value json; - dicom->ToJson(json, Plugins::Convert(p.format), - static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength); + dicom->DatasetToJson(json, Plugins::Convert(p.format), + static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength); Json::FastWriter writer; *p.result = CopyString(writer.write(json)); } - bool OrthancPlugins::InvokeService(SharedLibrary& plugin, - _OrthancPluginService service, - const void* parameters) + void OrthancPlugins::ApplyCreateDicom(_OrthancPluginService service, + const void* parameters) + { + const _OrthancPluginCreateDicom& p = + *reinterpret_cast<const _OrthancPluginCreateDicom*>(parameters); + + Json::Value json; + + if (p.json == NULL) + { + json = Json::objectValue; + } + else + { + Json::Reader reader; + if (!reader.parse(p.json, json)) + { + throw OrthancException(ErrorCode_BadJson); + } + } + + std::string dicom; + + { + std::auto_ptr<ParsedDicomFile> file + (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(p.flags))); + + if (p.pixelData) + { + file->EmbedImage(*reinterpret_cast<const ImageAccessor*>(p.pixelData)); + } + + file->SaveToMemoryBuffer(dicom); + } + + CopyToMemoryBuffer(*p.target, dicom); + } + + + void OrthancPlugins::ComputeHash(_OrthancPluginService service, + const void* parameters) + { + const _OrthancPluginComputeHash& p = + *reinterpret_cast<const _OrthancPluginComputeHash*>(parameters); + + std::string hash; + switch (service) + { + case _OrthancPluginService_ComputeMd5: + Toolbox::ComputeMD5(hash, p.buffer, p.size); + break; + + case _OrthancPluginService_ComputeSha1: + Toolbox::ComputeSHA1(hash, p.buffer, p.size); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + *p.result = CopyString(hash); + } + + + void OrthancPlugins::ApplyCreateImage(_OrthancPluginService service, + const void* parameters) + { + const _OrthancPluginCreateImage& p = + *reinterpret_cast<const _OrthancPluginCreateImage*>(parameters); + + std::auto_ptr<ImageAccessor> result; + + switch (service) + { + case _OrthancPluginService_CreateImage: + result.reset(new Image(Plugins::Convert(p.format), p.width, p.height, false)); + break; + + case _OrthancPluginService_CreateImageAccessor: + result.reset(new ImageAccessor); + result->AssignWritable(Plugins::Convert(p.format), p.width, p.height, p.pitch, p.buffer); + break; + + case _OrthancPluginService_DecodeDicomImage: + { + result.reset(Decode(p.constBuffer, p.bufferSize, p.frameIndex)); + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + *(p.target) = ReturnImage(result); + } + + + void OrthancPlugins::ApplySendMultipartItem(const void* parameters) { - VLOG(1) << "Calling service " << service << " from plugin " << plugin.GetPath(); - - boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); + // An exception might be raised in this function if the + // connection was closed by the HTTP client. + const _OrthancPluginAnswerBuffer& p = + *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters); + + HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output); + + std::map<std::string, std::string> headers; // No custom headers + output->SendMultipartItem(p.answer, p.answerSize, headers); + } + + + void OrthancPlugins::ApplySendMultipartItem2(const void* parameters) + { + // An exception might be raised in this function if the + // connection was closed by the HTTP client. + const _OrthancPluginSendMultipartItem2& p = + *reinterpret_cast<const _OrthancPluginSendMultipartItem2*>(parameters); + HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output); + + std::map<std::string, std::string> headers; + for (uint32_t i = 0; i < p.headersCount; i++) + { + headers[p.headersKeys[i]] = p.headersValues[i]; + } + + output->SendMultipartItem(p.answer, p.answerSize, headers); + } + + + void OrthancPlugins::DatabaseAnswer(const void* parameters) + { + const _OrthancPluginDatabaseAnswer& p = + *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters); + + if (pimpl_->database_.get() != NULL) + { + pimpl_->database_->AnswerReceived(p); + } + else + { + LOG(ERROR) << "Cannot invoke this service without a custom database back-end"; + throw OrthancException(ErrorCode_BadRequest); + } + } + + + namespace + { + class DictionaryReadLocker + { + private: + const DcmDataDictionary& dictionary_; + + public: + DictionaryReadLocker() : dictionary_(dcmDataDict.rdlock()) + { + } + + ~DictionaryReadLocker() + { + dcmDataDict.unlock(); + } + + const DcmDataDictionary* operator->() + { + return &dictionary_; + } + }; + } + + + void OrthancPlugins::ApplyLookupDictionary(const void* parameters) + { + const _OrthancPluginLookupDictionary& p = + *reinterpret_cast<const _OrthancPluginLookupDictionary*>(parameters); + + DicomTag tag(FromDcmtkBridge::ParseTag(p.name)); + DcmTagKey tag2(tag.GetGroup(), tag.GetElement()); + + DictionaryReadLocker locker; + const DcmDictEntry* entry = locker->findEntry(tag2, NULL); + + if (entry == NULL) + { + throw OrthancException(ErrorCode_UnknownDicomTag); + } + else + { + p.target->group = entry->getKey().getGroup(); + p.target->element = entry->getKey().getElement(); + p.target->vr = Plugins::Convert(FromDcmtkBridge::Convert(entry->getEVR())); + p.target->minMultiplicity = static_cast<uint32_t>(entry->getVMMin()); + p.target->maxMultiplicity = (entry->getVMMax() == DcmVariableVM ? 0 : static_cast<uint32_t>(entry->getVMMax())); + } + } + + + bool OrthancPlugins::InvokeSafeService(SharedLibrary& plugin, + _OrthancPluginService service, + const void* parameters) + { + // Services that can be run without mutual exclusion switch (service) { case _OrthancPluginService_GetOrthancPath: { - std::string s = Toolbox::GetPathToExecutable(); + std::string s = SystemToolbox::GetPathToExecutable(); *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s); return true; } case _OrthancPluginService_GetOrthancDirectory: { - std::string s = Toolbox::GetDirectoryOfExecutable(); + std::string s = SystemToolbox::GetDirectoryOfExecutable(); *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s); return true; } @@ -1325,22 +2410,6 @@ BufferCompression(parameters); return true; - case _OrthancPluginService_RegisterRestCallback: - RegisterRestCallback(parameters, true); - return true; - - case _OrthancPluginService_RegisterRestCallbackNoLock: - RegisterRestCallback(parameters, false); - return true; - - case _OrthancPluginService_RegisterOnStoredInstanceCallback: - RegisterOnStoredInstanceCallback(parameters); - return true; - - case _OrthancPluginService_RegisterOnChangeCallback: - RegisterOnChangeCallback(parameters); - return true; - case _OrthancPluginService_AnswerBuffer: AnswerBuffer(parameters); return true; @@ -1365,6 +2434,10 @@ RestApiGet(parameters, true); return true; + case _OrthancPluginService_RestApiGet2: + RestApiGet2(parameters); + return true; + case _OrthancPluginService_RestApiPost: RestApiPostPut(true, parameters, false); return true; @@ -1432,9 +2505,538 @@ case _OrthancPluginService_GetInstanceSimplifiedJson: case _OrthancPluginService_HasInstanceMetadata: case _OrthancPluginService_GetInstanceMetadata: + case _OrthancPluginService_GetInstanceOrigin: AccessDicomInstance(service, parameters); return true; + case _OrthancPluginService_SetGlobalProperty: + { + const _OrthancPluginGlobalProperty& p = + *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters); + if (p.property < 1024) + { + return false; + } + else + { + PImpl::ServerContextLock lock(*pimpl_); + lock.GetContext().GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value); + return true; + } + } + + case _OrthancPluginService_GetGlobalProperty: + { + const _OrthancPluginGlobalProperty& p = + *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters); + + std::string result; + + { + PImpl::ServerContextLock lock(*pimpl_); + result = lock.GetContext().GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value); + } + + *(p.result) = CopyString(result); + return true; + } + + case _OrthancPluginService_GetExpectedDatabaseVersion: + { + const _OrthancPluginReturnSingleValue& p = + *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters); + *(p.resultUint32) = ORTHANC_DATABASE_VERSION; + return true; + } + + case _OrthancPluginService_StartMultipartAnswer: + { + const _OrthancPluginStartMultipartAnswer& p = + *reinterpret_cast<const _OrthancPluginStartMultipartAnswer*>(parameters); + HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output); + output->StartMultipart(p.subType, p.contentType); + return true; + } + + case _OrthancPluginService_SendMultipartItem: + ApplySendMultipartItem(parameters); + return true; + + case _OrthancPluginService_SendMultipartItem2: + ApplySendMultipartItem2(parameters); + return true; + + case _OrthancPluginService_ReadFile: + { + const _OrthancPluginReadFile& p = + *reinterpret_cast<const _OrthancPluginReadFile*>(parameters); + + std::string content; + SystemToolbox::ReadFile(content, p.path); + CopyToMemoryBuffer(*p.target, content.size() > 0 ? content.c_str() : NULL, content.size()); + + return true; + } + + case _OrthancPluginService_WriteFile: + { + const _OrthancPluginWriteFile& p = + *reinterpret_cast<const _OrthancPluginWriteFile*>(parameters); + SystemToolbox::WriteFile(p.data, p.size, p.path); + return true; + } + + case _OrthancPluginService_GetErrorDescription: + { + const _OrthancPluginGetErrorDescription& p = + *reinterpret_cast<const _OrthancPluginGetErrorDescription*>(parameters); + *(p.target) = EnumerationToString(static_cast<ErrorCode>(p.error)); + return true; + } + + case _OrthancPluginService_GetImagePixelFormat: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultPixelFormat) = Plugins::Convert(reinterpret_cast<const ImageAccessor*>(p.image)->GetFormat()); + return true; + } + + case _OrthancPluginService_GetImageWidth: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetWidth(); + return true; + } + + case _OrthancPluginService_GetImageHeight: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetHeight(); + return true; + } + + case _OrthancPluginService_GetImagePitch: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetPitch(); + return true; + } + + case _OrthancPluginService_GetImageBuffer: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultBuffer) = reinterpret_cast<const ImageAccessor*>(p.image)->GetBuffer(); + return true; + } + + case _OrthancPluginService_FreeImage: + { + const _OrthancPluginFreeImage& p = *reinterpret_cast<const _OrthancPluginFreeImage*>(parameters); + + if (p.image != NULL) + { + delete reinterpret_cast<ImageAccessor*>(p.image); + } + + return true; + } + + case _OrthancPluginService_UncompressImage: + UncompressImage(parameters); + return true; + + case _OrthancPluginService_CompressImage: + CompressImage(parameters); + return true; + + case _OrthancPluginService_CallHttpClient: + CallHttpClient(parameters); + return true; + + case _OrthancPluginService_CallHttpClient2: + CallHttpClient2(parameters); + return true; + + case _OrthancPluginService_ConvertPixelFormat: + ConvertPixelFormat(parameters); + return true; + + case _OrthancPluginService_GetFontsCount: + { + const _OrthancPluginReturnSingleValue& p = + *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters); + *(p.resultUint32) = Configuration::GetFontRegistry().GetSize(); + return true; + } + + case _OrthancPluginService_GetFontInfo: + GetFontInfo(parameters); + return true; + + case _OrthancPluginService_DrawText: + DrawText(parameters); + return true; + + case _OrthancPluginService_StorageAreaCreate: + { + const _OrthancPluginStorageAreaCreate& p = + *reinterpret_cast<const _OrthancPluginStorageAreaCreate*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + storage.Create(p.uuid, p.content, static_cast<size_t>(p.size), Plugins::Convert(p.type)); + return true; + } + + case _OrthancPluginService_StorageAreaRead: + { + const _OrthancPluginStorageAreaRead& p = + *reinterpret_cast<const _OrthancPluginStorageAreaRead*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + std::string content; + storage.Read(content, p.uuid, Plugins::Convert(p.type)); + CopyToMemoryBuffer(*p.target, content); + return true; + } + + case _OrthancPluginService_StorageAreaRemove: + { + const _OrthancPluginStorageAreaRemove& p = + *reinterpret_cast<const _OrthancPluginStorageAreaRemove*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + storage.Remove(p.uuid, Plugins::Convert(p.type)); + return true; + } + + case _OrthancPluginService_DicomBufferToJson: + case _OrthancPluginService_DicomInstanceToJson: + ApplyDicomToJson(service, parameters); + return true; + + case _OrthancPluginService_CreateDicom: + ApplyCreateDicom(service, parameters); + return true; + + case _OrthancPluginService_WorklistAddAnswer: + { + const _OrthancPluginWorklistAnswersOperation& p = + *reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters); + reinterpret_cast<const WorklistHandler*>(p.query)->AddAnswer(p.answers, p.dicom, p.size); + return true; + } + + case _OrthancPluginService_WorklistMarkIncomplete: + { + const _OrthancPluginWorklistAnswersOperation& p = + *reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters); + reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false); + return true; + } + + case _OrthancPluginService_WorklistIsMatch: + { + const _OrthancPluginWorklistQueryOperation& p = + *reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters); + *p.isMatch = reinterpret_cast<const WorklistHandler*>(p.query)->IsMatch(p.dicom, p.size); + return true; + } + + case _OrthancPluginService_WorklistGetDicomQuery: + { + const _OrthancPluginWorklistQueryOperation& p = + *reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters); + reinterpret_cast<const WorklistHandler*>(p.query)->GetDicomQuery(*p.target); + return true; + } + + case _OrthancPluginService_FindAddAnswer: + { + const _OrthancPluginFindOperation& p = + *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters); + reinterpret_cast<DicomFindAnswers*>(p.answers)->Add(p.dicom, p.size); + return true; + } + + case _OrthancPluginService_FindMarkIncomplete: + { + const _OrthancPluginFindOperation& p = + *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters); + reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false); + return true; + } + + case _OrthancPluginService_GetFindQuerySize: + case _OrthancPluginService_GetFindQueryTag: + case _OrthancPluginService_GetFindQueryTagName: + case _OrthancPluginService_GetFindQueryValue: + { + const _OrthancPluginFindOperation& p = + *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters); + reinterpret_cast<const FindHandler*>(p.query)->Invoke(service, p); + return true; + } + + case _OrthancPluginService_CreateImage: + case _OrthancPluginService_CreateImageAccessor: + case _OrthancPluginService_DecodeDicomImage: + ApplyCreateImage(service, parameters); + return true; + + case _OrthancPluginService_ComputeMd5: + case _OrthancPluginService_ComputeSha1: + ComputeHash(service, parameters); + return true; + + case _OrthancPluginService_LookupDictionary: + ApplyLookupDictionary(parameters); + return true; + + case _OrthancPluginService_GenerateUuid: + { + *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = + CopyString(Toolbox::GenerateUuid()); + return true; + } + + case _OrthancPluginService_CreateFindMatcher: + { + const _OrthancPluginCreateFindMatcher& p = + *reinterpret_cast<const _OrthancPluginCreateFindMatcher*>(parameters); + ParsedDicomFile query(p.query, p.size); + *(p.target) = reinterpret_cast<OrthancPluginFindMatcher*>(new HierarchicalMatcher(query)); + return true; + } + + case _OrthancPluginService_FreeFindMatcher: + { + const _OrthancPluginFreeFindMatcher& p = + *reinterpret_cast<const _OrthancPluginFreeFindMatcher*>(parameters); + + if (p.matcher != NULL) + { + delete reinterpret_cast<HierarchicalMatcher*>(p.matcher); + } + + return true; + } + + case _OrthancPluginService_FindMatcherIsMatch: + { + const _OrthancPluginFindMatcherIsMatch& p = + *reinterpret_cast<const _OrthancPluginFindMatcherIsMatch*>(parameters); + + if (p.matcher == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + ParsedDicomFile query(p.dicom, p.size); + *p.isMatch = reinterpret_cast<const HierarchicalMatcher*>(p.matcher)->Match(query) ? 1 : 0; + return true; + } + } + + case _OrthancPluginService_GetPeers: + { + const _OrthancPluginGetPeers& p = + *reinterpret_cast<const _OrthancPluginGetPeers*>(parameters); + *(p.peers) = reinterpret_cast<OrthancPluginPeers*>(new OrthancPeers); + return true; + } + + case _OrthancPluginService_FreePeers: + { + const _OrthancPluginFreePeers& p = + *reinterpret_cast<const _OrthancPluginFreePeers*>(parameters); + + if (p.peers != NULL) + { + delete reinterpret_cast<OrthancPeers*>(p.peers); + } + + return true; + } + + case _OrthancPluginService_GetPeersCount: + { + const _OrthancPluginGetPeersCount& p = + *reinterpret_cast<const _OrthancPluginGetPeersCount*>(parameters); + + if (p.peers == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + *(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeersCount(); + return true; + } + } + + case _OrthancPluginService_GetPeerName: + { + const _OrthancPluginGetPeerProperty& p = + *reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters); + + if (p.peers == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + *(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerName(p.peerIndex).c_str(); + return true; + } + } + + case _OrthancPluginService_GetPeerUrl: + { + const _OrthancPluginGetPeerProperty& p = + *reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters); + + if (p.peers == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + *(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUrl().c_str(); + return true; + } + } + + case _OrthancPluginService_GetPeerUserProperty: + { + const _OrthancPluginGetPeerProperty& p = + *reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters); + + if (p.peers == NULL || + p.userProperty == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + const WebServiceParameters::Dictionary& properties = + reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUserProperties(); + + WebServiceParameters::Dictionary::const_iterator found = + properties.find(p.userProperty); + + if (found == properties.end()) + { + *(p.target) = NULL; + } + else + { + *(p.target) = found->second.c_str(); + } + + return true; + } + } + + case _OrthancPluginService_CallPeerApi: + CallPeerApi(parameters); + return true; + + case _OrthancPluginService_CreateJob: + { + const _OrthancPluginCreateJob& p = + *reinterpret_cast<const _OrthancPluginCreateJob*>(parameters); + *(p.target) = reinterpret_cast<OrthancPluginJob*>(new PluginsJob(p)); + return true; + } + + case _OrthancPluginService_FreeJob: + { + const _OrthancPluginFreeJob& p = + *reinterpret_cast<const _OrthancPluginFreeJob*>(parameters); + + if (p.job != NULL) + { + delete reinterpret_cast<PluginsJob*>(p.job); + } + + return true; + } + + case _OrthancPluginService_SubmitJob: + { + const _OrthancPluginSubmitJob& p = + *reinterpret_cast<const _OrthancPluginSubmitJob*>(parameters); + + std::string uuid; + + PImpl::ServerContextLock lock(*pimpl_); + lock.GetContext().GetJobsEngine().GetRegistry().Submit + (uuid, reinterpret_cast<PluginsJob*>(p.job), p.priority); + + *p.resultId = CopyString(uuid); + + return true; + } + + default: + return false; + } + } + + + + bool OrthancPlugins::InvokeProtectedService(SharedLibrary& plugin, + _OrthancPluginService service, + const void* parameters) + { + // Services that must be run in mutual exclusion. Guideline: + // Whenever "pimpl_" is directly accessed by the service, it + // should be listed here. + + switch (service) + { + case _OrthancPluginService_RegisterRestCallback: + RegisterRestCallback(parameters, true); + return true; + + case _OrthancPluginService_RegisterRestCallbackNoLock: + RegisterRestCallback(parameters, false); + return true; + + case _OrthancPluginService_RegisterOnStoredInstanceCallback: + RegisterOnStoredInstanceCallback(parameters); + return true; + + case _OrthancPluginService_RegisterOnChangeCallback: + RegisterOnChangeCallback(parameters); + return true; + + case _OrthancPluginService_RegisterWorklistCallback: + RegisterWorklistCallback(parameters); + return true; + + case _OrthancPluginService_RegisterFindCallback: + RegisterFindCallback(parameters); + return true; + + case _OrthancPluginService_RegisterMoveCallback: + RegisterMoveCallback(parameters); + return true; + + case _OrthancPluginService_RegisterDecodeImageCallback: + RegisterDecodeImageCallback(parameters); + return true; + + case _OrthancPluginService_RegisterJobsUnserializer: + RegisterJobsUnserializer(parameters); + return true; + + case _OrthancPluginService_RegisterIncomingHttpRequestFilter: + RegisterIncomingHttpRequestFilter(parameters); + return true; + + case _OrthancPluginService_RegisterIncomingHttpRequestFilter2: + RegisterIncomingHttpRequestFilter2(parameters); + return true; + case _OrthancPluginService_RegisterStorageArea: { LOG(INFO) << "Plugin has registered a custom storage area"; @@ -1461,33 +3063,6 @@ return true; } - case _OrthancPluginService_SetGlobalProperty: - { - const _OrthancPluginGlobalProperty& p = - *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters); - if (p.property < 1024) - { - return false; - } - else - { - CheckContextAvailable(); - pimpl_->context_->GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value); - return true; - } - } - - case _OrthancPluginService_GetGlobalProperty: - { - CheckContextAvailable(); - - const _OrthancPluginGlobalProperty& p = - *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters); - std::string result = pimpl_->context_->GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value); - *(p.result) = CopyString(result); - return true; - } - case _OrthancPluginService_GetCommandLineArgumentsCount: { const _OrthancPluginReturnSingleValue& p = @@ -1559,187 +3134,7 @@ } case _OrthancPluginService_DatabaseAnswer: - { - const _OrthancPluginDatabaseAnswer& p = - *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters); - - if (pimpl_->database_.get() != NULL) - { - pimpl_->database_->AnswerReceived(p); - return true; - } - else - { - LOG(ERROR) << "Cannot invoke this service without a custom database back-end"; - throw OrthancException(ErrorCode_BadRequest); - } - } - - case _OrthancPluginService_GetExpectedDatabaseVersion: - { - const _OrthancPluginReturnSingleValue& p = - *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters); - *(p.resultUint32) = ORTHANC_DATABASE_VERSION; - return true; - } - - case _OrthancPluginService_StartMultipartAnswer: - { - const _OrthancPluginStartMultipartAnswer& p = - *reinterpret_cast<const _OrthancPluginStartMultipartAnswer*>(parameters); - HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output); - output->StartMultipart(p.subType, p.contentType); - return true; - } - - case _OrthancPluginService_SendMultipartItem: - { - // An exception might be raised in this function if the - // connection was closed by the HTTP client. - const _OrthancPluginAnswerBuffer& p = - *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters); - HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output); - output->SendMultipartItem(p.answer, p.answerSize); - return true; - } - - case _OrthancPluginService_ReadFile: - { - const _OrthancPluginReadFile& p = - *reinterpret_cast<const _OrthancPluginReadFile*>(parameters); - - std::string content; - Toolbox::ReadFile(content, p.path); - CopyToMemoryBuffer(*p.target, content.size() > 0 ? content.c_str() : NULL, content.size()); - - return true; - } - - case _OrthancPluginService_WriteFile: - { - const _OrthancPluginWriteFile& p = - *reinterpret_cast<const _OrthancPluginWriteFile*>(parameters); - Toolbox::WriteFile(p.data, p.size, p.path); - return true; - } - - case _OrthancPluginService_GetErrorDescription: - { - const _OrthancPluginGetErrorDescription& p = - *reinterpret_cast<const _OrthancPluginGetErrorDescription*>(parameters); - *(p.target) = EnumerationToString(static_cast<ErrorCode>(p.error)); - return true; - } - - case _OrthancPluginService_GetImagePixelFormat: - { - const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); - *(p.resultPixelFormat) = Plugins::Convert(reinterpret_cast<const ImageAccessor*>(p.image)->GetFormat()); - return true; - } - - case _OrthancPluginService_GetImageWidth: - { - const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); - *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetWidth(); - return true; - } - - case _OrthancPluginService_GetImageHeight: - { - const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); - *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetHeight(); - return true; - } - - case _OrthancPluginService_GetImagePitch: - { - const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); - *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetPitch(); - return true; - } - - case _OrthancPluginService_GetImageBuffer: - { - const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); - *(p.resultBuffer) = reinterpret_cast<const ImageAccessor*>(p.image)->GetConstBuffer(); - return true; - } - - case _OrthancPluginService_FreeImage: - { - const _OrthancPluginFreeImage& p = *reinterpret_cast<const _OrthancPluginFreeImage*>(parameters); - if (p.image == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - delete reinterpret_cast<ImageAccessor*>(p.image); - return true; - } - } - - case _OrthancPluginService_UncompressImage: - UncompressImage(parameters); - return true; - - case _OrthancPluginService_CompressImage: - CompressImage(parameters); - return true; - - case _OrthancPluginService_CallHttpClient: - CallHttpClient(parameters); - return true; - - case _OrthancPluginService_ConvertPixelFormat: - ConvertPixelFormat(parameters); - return true; - - case _OrthancPluginService_GetFontsCount: - { - const _OrthancPluginReturnSingleValue& p = - *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters); - *(p.resultUint32) = Configuration::GetFontRegistry().GetSize(); - return true; - } - - case _OrthancPluginService_GetFontInfo: - GetFontInfo(parameters); - return true; - - case _OrthancPluginService_DrawText: - DrawText(parameters); - return true; - - case _OrthancPluginService_StorageAreaCreate: - { - const _OrthancPluginStorageAreaCreate& p = - *reinterpret_cast<const _OrthancPluginStorageAreaCreate*>(parameters); - IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); - storage.Create(p.uuid, p.content, p.size, Plugins::Convert(p.type)); - return true; - } - - case _OrthancPluginService_StorageAreaRead: - { - const _OrthancPluginStorageAreaRead& p = - *reinterpret_cast<const _OrthancPluginStorageAreaRead*>(parameters); - IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); - std::string content; - storage.Read(content, p.uuid, Plugins::Convert(p.type)); - CopyToMemoryBuffer(*p.target, content); - return true; - } - - case _OrthancPluginService_StorageAreaRemove: - { - const _OrthancPluginStorageAreaRemove& p = - *reinterpret_cast<const _OrthancPluginStorageAreaRemove*>(parameters); - IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); - storage.Remove(p.uuid, Plugins::Convert(p.type)); - return true; - } + throw OrthancException(ErrorCode_InternalError); // Implemented before locking (*) case _OrthancPluginService_RegisterErrorCode: { @@ -1755,7 +3150,17 @@ *reinterpret_cast<const _OrthancPluginRegisterDictionaryTag*>(parameters); FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element), Plugins::Convert(p.vr), p.name, - p.minMultiplicity, p.maxMultiplicity); + p.minMultiplicity, p.maxMultiplicity, ""); + return true; + } + + case _OrthancPluginService_RegisterPrivateDictionaryTag: + { + const _OrthancPluginRegisterPrivateDictionaryTag& p = + *reinterpret_cast<const _OrthancPluginRegisterPrivateDictionaryTag*>(parameters); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element), + Plugins::Convert(p.vr), p.name, + p.minMultiplicity, p.maxMultiplicity, p.privateCreator); return true; } @@ -1771,16 +3176,11 @@ } IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); - Toolbox::ReconstructMainDicomTags(*pimpl_->database_, storage, Plugins::Convert(p.level)); + ServerToolbox::ReconstructMainDicomTags(*pimpl_->database_, storage, Plugins::Convert(p.level)); return true; } - case _OrthancPluginService_DicomBufferToJson: - case _OrthancPluginService_DicomInstanceToJson: - ApplyDicomToJson(service, parameters); - return true; - default: { // This service is unknown to the Orthanc plugin engine @@ -1790,13 +3190,48 @@ } + + bool OrthancPlugins::InvokeService(SharedLibrary& plugin, + _OrthancPluginService service, + const void* parameters) + { + VLOG(1) << "Calling service " << service << " from plugin " << plugin.GetPath(); + + if (service == _OrthancPluginService_DatabaseAnswer) + { + // This case solves a deadlock at (*) reported by James Webster + // on 2015-10-27 that was present in versions of Orthanc <= + // 0.9.4 and related to database plugins implementing a custom + // index. The problem was that locking the database is already + // ensured by the "ServerIndex" class if the invoked service is + // "DatabaseAnswer". + DatabaseAnswer(parameters); + return true; + } + + if (InvokeSafeService(plugin, service, parameters)) + { + // The invoked service does not require locking + return true; + } + else + { + // The invoked service requires locking + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); // (*) + return InvokeProtectedService(plugin, service, parameters); + } + } + + bool OrthancPlugins::HasStorageArea() const { + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); return pimpl_->storageArea_.get() != NULL; } bool OrthancPlugins::HasDatabaseBackend() const { + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); return pimpl_->database_.get() != NULL; } @@ -1898,4 +3333,210 @@ { return pimpl_->dictionary_; } + + + IWorklistRequestHandler* OrthancPlugins::ConstructWorklistRequestHandler() + { + if (HasWorklistHandler()) + { + return new WorklistHandler(*this); + } + else + { + return NULL; + } + } + + + bool OrthancPlugins::HasWorklistHandler() + { + boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_); + return pimpl_->worklistCallback_ != NULL; + } + + + IFindRequestHandler* OrthancPlugins::ConstructFindRequestHandler() + { + if (HasFindHandler()) + { + return new FindHandler(*this); + } + else + { + return NULL; + } + } + + + bool OrthancPlugins::HasFindHandler() + { + boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_); + return pimpl_->findCallback_ != NULL; + } + + + IMoveRequestHandler* OrthancPlugins::ConstructMoveRequestHandler() + { + if (HasMoveHandler()) + { + return new MoveHandler(*this); + } + else + { + return NULL; + } + } + + + bool OrthancPlugins::HasMoveHandler() + { + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); + return pimpl_->moveCallbacks_.callback != NULL; + } + + + bool OrthancPlugins::HasCustomImageDecoder() + { + boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_); + return !pimpl_->decodeImageCallbacks_.empty(); + } + + + ImageAccessor* OrthancPlugins::DecodeUnsafe(const void* dicom, + size_t size, + unsigned int frame) + { + boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_); + + for (PImpl::DecodeImageCallbacks::const_iterator + decoder = pimpl_->decodeImageCallbacks_.begin(); + decoder != pimpl_->decodeImageCallbacks_.end(); ++decoder) + { + OrthancPluginImage* pluginImage = NULL; + if ((*decoder) (&pluginImage, dicom, size, frame) == OrthancPluginErrorCode_Success && + pluginImage != NULL) + { + return reinterpret_cast<ImageAccessor*>(pluginImage); + } + } + + return NULL; + } + + + ImageAccessor* OrthancPlugins::Decode(const void* dicom, + size_t size, + unsigned int frame) + { + ImageAccessor* result = DecodeUnsafe(dicom, size, frame); + + if (result != NULL) + { + return result; + } + else + { + LOG(INFO) << "The installed image decoding plugins cannot handle an image, fallback to the built-in decoder"; + DefaultDicomImageDecoder defaultDecoder; + return defaultDecoder.Decode(dicom, size, frame); + } + } + + + bool OrthancPlugins::IsAllowed(HttpMethod method, + const char* uri, + const char* ip, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::GetArguments& getArguments) + { + OrthancPluginHttpMethod cMethod = Plugins::Convert(method); + + std::vector<const char*> httpKeys(httpHeaders.size()); + std::vector<const char*> httpValues(httpHeaders.size()); + + size_t pos = 0; + for (IHttpHandler::Arguments::const_iterator + it = httpHeaders.begin(); it != httpHeaders.end(); ++it, pos++) + { + httpKeys[pos] = it->first.c_str(); + httpValues[pos] = it->second.c_str(); + } + + std::vector<const char*> getKeys(getArguments.size()); + std::vector<const char*> getValues(getArguments.size()); + + for (size_t i = 0; i < getArguments.size(); i++) + { + getKeys[i] = getArguments[i].first.c_str(); + getValues[i] = getArguments[i].second.c_str(); + } + + // Improved callback with support for GET arguments, since Orthanc 1.3.0 + for (PImpl::IncomingHttpRequestFilters2::const_iterator + filter = pimpl_->incomingHttpRequestFilters2_.begin(); + filter != pimpl_->incomingHttpRequestFilters2_.end(); ++filter) + { + int32_t allowed = (*filter) (cMethod, uri, ip, + httpKeys.size(), + httpKeys.empty() ? NULL : &httpKeys[0], + httpValues.empty() ? NULL : &httpValues[0], + getKeys.size(), + getKeys.empty() ? NULL : &getKeys[0], + getValues.empty() ? NULL : &getValues[0]); + + if (allowed == 0) + { + return false; + } + else if (allowed != 1) + { + // The callback is only allowed to answer 0 or 1 + throw OrthancException(ErrorCode_Plugin); + } + } + + for (PImpl::IncomingHttpRequestFilters::const_iterator + filter = pimpl_->incomingHttpRequestFilters_.begin(); + filter != pimpl_->incomingHttpRequestFilters_.end(); ++filter) + { + int32_t allowed = (*filter) (cMethod, uri, ip, httpKeys.size(), + httpKeys.empty() ? NULL : &httpKeys[0], + httpValues.empty() ? NULL : &httpValues[0]); + + if (allowed == 0) + { + return false; + } + else if (allowed != 1) + { + // The callback is only allowed to answer 0 or 1 + throw OrthancException(ErrorCode_Plugin); + } + } + + return true; + } + + + IJob* OrthancPlugins::UnserializeJob(const std::string& type, + const Json::Value& value) + { + const std::string serialized = value.toStyledString(); + + boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_); + + for (PImpl::JobsUnserializers::iterator + unserializer = pimpl_->jobsUnserializers_.begin(); + unserializer != pimpl_->jobsUnserializers_.end(); ++unserializer) + { + OrthancPluginJob* job = (*unserializer) (type.c_str(), serialized.c_str()); + if (job != NULL) + { + return reinterpret_cast<PluginsJob*>(job); + } + } + + return NULL; + } }
--- a/Plugins/Engine/OrthancPlugins.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +35,12 @@ #include "PluginsErrorDictionary.h" -#if ORTHANC_PLUGINS_ENABLED != 1 +#if !defined(ORTHANC_ENABLE_PLUGINS) +# error The macro ORTHANC_ENABLE_PLUGINS must be defined +#endif + + +#if ORTHANC_ENABLE_PLUGINS != 1 #include <boost/noncopyable.hpp> @@ -47,8 +53,14 @@ #else +#include "../../Core/DicomNetworking/IFindRequestHandlerFactory.h" +#include "../../Core/DicomNetworking/IMoveRequestHandlerFactory.h" +#include "../../Core/DicomNetworking/IWorklistRequestHandlerFactory.h" #include "../../Core/FileStorage/IStorageArea.h" #include "../../Core/HttpServer/IHttpHandler.h" +#include "../../Core/HttpServer/IIncomingHttpRequestFilter.h" +#include "../../Core/JobsEngine/IJob.h" +#include "../../OrthancServer/IDicomImageDecoder.h" #include "../../OrthancServer/IServerListener.h" #include "OrthancPluginDatabase.h" #include "PluginsManager.h" @@ -63,13 +75,20 @@ class OrthancPlugins : public IHttpHandler, public IPluginServiceProvider, - public IServerListener + public IServerListener, + public IWorklistRequestHandlerFactory, + public IDicomImageDecoder, + public IIncomingHttpRequestFilter, + public IFindRequestHandlerFactory, + public IMoveRequestHandlerFactory { private: - struct PImpl; + class PImpl; boost::shared_ptr<PImpl> pimpl_; - void CheckContextAvailable(); + class WorklistHandler; + class FindHandler; + class MoveHandler; void RegisterRestCallback(const void* parameters, bool lock); @@ -78,6 +97,20 @@ void RegisterOnChangeCallback(const void* parameters); + void RegisterWorklistCallback(const void* parameters); + + void RegisterFindCallback(const void* parameters); + + void RegisterMoveCallback(const void* parameters); + + void RegisterDecodeImageCallback(const void* parameters); + + void RegisterJobsUnserializer(const void* parameters); + + void RegisterIncomingHttpRequestFilter(const void* parameters); + + void RegisterIncomingHttpRequestFilter2(const void* parameters); + void AnswerBuffer(const void* parameters); void Redirect(const void* parameters); @@ -91,6 +124,8 @@ void RestApiGet(const void* parameters, bool afterPlugins); + void RestApiGet2(const void* parameters); + void RestApiPostPut(bool isPost, const void* parameters, bool afterPlugins); @@ -123,17 +158,46 @@ void CallHttpClient(const void* parameters); + void CallHttpClient2(const void* parameters); + + void CallPeerApi(const void* parameters); + void GetFontInfo(const void* parameters); void DrawText(const void* parameters); + void DatabaseAnswer(const void* parameters); + void ApplyDicomToJson(_OrthancPluginService service, const void* parameters); + void ApplyCreateDicom(_OrthancPluginService service, + const void* parameters); + + void ApplyCreateImage(_OrthancPluginService service, + const void* parameters); + + void ApplyLookupDictionary(const void* parameters); + + void ApplySendMultipartItem(const void* parameters); + + void ApplySendMultipartItem2(const void* parameters); + + void ComputeHash(_OrthancPluginService service, + const void* parameters); + void SignalChangeInternal(OrthancPluginChangeType changeType, OrthancPluginResourceType resourceType, const char* resource); + bool InvokeSafeService(SharedLibrary& plugin, + _OrthancPluginService service, + const void* parameters); + + bool InvokeProtectedService(SharedLibrary& plugin, + _OrthancPluginService service, + const void* parameters); + public: OrthancPlugins(); @@ -141,6 +205,8 @@ void SetServerContext(ServerContext& context); + void ResetServerContext(); + virtual bool Handle(HttpOutput& output, RequestOrigin origin, const char* remoteIp, @@ -200,6 +266,51 @@ { SignalChangeInternal(OrthancPluginChangeType_OrthancStopped, OrthancPluginResourceType_None, NULL); } + + void SignalUpdatedPeers() + { + SignalChangeInternal(OrthancPluginChangeType_UpdatedPeers, OrthancPluginResourceType_None, NULL); + } + + void SignalUpdatedModalities() + { + SignalChangeInternal(OrthancPluginChangeType_UpdatedModalities, OrthancPluginResourceType_None, NULL); + } + + bool HasWorklistHandler(); + + virtual IWorklistRequestHandler* ConstructWorklistRequestHandler(); + + bool HasCustomImageDecoder(); + + // Contrarily to "Decode()", this method does not fallback to the + // builtin image decoder, if no installed custom decoder can + // handle the image (it returns NULL in this case). + ImageAccessor* DecodeUnsafe(const void* dicom, + size_t size, + unsigned int frame); + + virtual ImageAccessor* Decode(const void* dicom, + size_t size, + unsigned int frame); + + virtual bool IsAllowed(HttpMethod method, + const char* uri, + const char* ip, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::GetArguments& getArguments); + + bool HasFindHandler(); + + virtual IFindRequestHandler* ConstructFindRequestHandler(); + + bool HasMoveHandler(); + + virtual IMoveRequestHandler* ConstructMoveRequestHandler(); + + IJob* UnserializeJob(const std::string& type, + const Json::Value& value); }; }
--- a/Plugins/Engine/PluginsEnumerations.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/PluginsEnumerations.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ #include "../../OrthancServer/PrecompiledHeadersServer.h" #include "PluginsEnumerations.h" -#if ORTHANC_PLUGINS_ENABLED != 1 +#if ORTHANC_ENABLE_PLUGINS != 1 #error The plugin support is disabled #endif @@ -122,6 +123,12 @@ case ChangeType_StableStudy: return OrthancPluginChangeType_StableStudy; + case ChangeType_UpdatedAttachment: + return OrthancPluginChangeType_UpdatedAttachment; + + case ChangeType_UpdatedMetadata: + return OrthancPluginChangeType_UpdatedMetadata; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -132,15 +139,30 @@ { switch (format) { + case PixelFormat_BGRA32: + return OrthancPluginPixelFormat_BGRA32; + + case PixelFormat_Float32: + return OrthancPluginPixelFormat_Float32; + case PixelFormat_Grayscale16: return OrthancPluginPixelFormat_Grayscale16; + case PixelFormat_Grayscale32: + return OrthancPluginPixelFormat_Grayscale32; + + case PixelFormat_Grayscale64: + return OrthancPluginPixelFormat_Grayscale64; + case PixelFormat_Grayscale8: return OrthancPluginPixelFormat_Grayscale8; case PixelFormat_RGB24: return OrthancPluginPixelFormat_RGB24; + case PixelFormat_RGB48: + return OrthancPluginPixelFormat_RGB48; + case PixelFormat_RGBA32: return OrthancPluginPixelFormat_RGBA32; @@ -157,15 +179,30 @@ { switch (format) { + case OrthancPluginPixelFormat_BGRA32: + return PixelFormat_BGRA32; + + case OrthancPluginPixelFormat_Float32: + return PixelFormat_Float32; + case OrthancPluginPixelFormat_Grayscale16: return PixelFormat_Grayscale16; + case OrthancPluginPixelFormat_Grayscale32: + return PixelFormat_Grayscale32; + + case OrthancPluginPixelFormat_Grayscale64: + return PixelFormat_Grayscale64; + case OrthancPluginPixelFormat_Grayscale8: return PixelFormat_Grayscale8; case OrthancPluginPixelFormat_RGB24: return PixelFormat_RGB24; + case OrthancPluginPixelFormat_RGB48: + return PixelFormat_RGB48; + case OrthancPluginPixelFormat_RGBA32: return PixelFormat_RGBA32; @@ -220,8 +257,8 @@ case OrthancPluginDicomToJsonFormat_Short: return DicomToJsonFormat_Short; - case OrthancPluginDicomToJsonFormat_Simple: - return DicomToJsonFormat_Simple; + case OrthancPluginDicomToJsonFormat_Human: + return DicomToJsonFormat_Human; default: throw OrthancException(ErrorCode_ParameterOutOfRange); @@ -273,96 +310,285 @@ } -#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 - DcmEVR Convert(OrthancPluginValueRepresentation vr) + OrthancPluginInstanceOrigin Convert(RequestOrigin origin) { - switch (vr) + switch (origin) { - case OrthancPluginValueRepresentation_AE: - return EVR_AE; - - case OrthancPluginValueRepresentation_AS: - return EVR_AS; - - case OrthancPluginValueRepresentation_AT: - return EVR_AT; - - case OrthancPluginValueRepresentation_CS: - return EVR_CS; - - case OrthancPluginValueRepresentation_DA: - return EVR_DA; + case RequestOrigin_DicomProtocol: + return OrthancPluginInstanceOrigin_DicomProtocol; - case OrthancPluginValueRepresentation_DS: - return EVR_DS; + case RequestOrigin_RestApi: + return OrthancPluginInstanceOrigin_RestApi; - case OrthancPluginValueRepresentation_DT: - return EVR_DT; - - case OrthancPluginValueRepresentation_FD: - return EVR_FD; + case RequestOrigin_Lua: + return OrthancPluginInstanceOrigin_Lua; - case OrthancPluginValueRepresentation_FL: - return EVR_FL; - - case OrthancPluginValueRepresentation_IS: - return EVR_IS; + case RequestOrigin_Plugins: + return OrthancPluginInstanceOrigin_Plugin; - case OrthancPluginValueRepresentation_LO: - return EVR_LO; - - case OrthancPluginValueRepresentation_LT: - return EVR_LT; + case RequestOrigin_Unknown: + return OrthancPluginInstanceOrigin_Unknown; - case OrthancPluginValueRepresentation_OB: - return EVR_OB; - - case OrthancPluginValueRepresentation_OF: - return EVR_OF; - - case OrthancPluginValueRepresentation_OW: - return EVR_OW; + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } - case OrthancPluginValueRepresentation_PN: - return EVR_PN; - - case OrthancPluginValueRepresentation_SH: - return EVR_SH; - - case OrthancPluginValueRepresentation_SL: - return EVR_SL; - - case OrthancPluginValueRepresentation_SQ: - return EVR_SQ; - case OrthancPluginValueRepresentation_SS: - return EVR_SS; - - case OrthancPluginValueRepresentation_ST: - return EVR_ST; - - case OrthancPluginValueRepresentation_TM: - return EVR_TM; - - case OrthancPluginValueRepresentation_UI: - return EVR_UI; + OrthancPluginHttpMethod Convert(HttpMethod method) + { + switch (method) + { + case HttpMethod_Get: + return OrthancPluginHttpMethod_Get; - case OrthancPluginValueRepresentation_UL: - return EVR_UL; - - case OrthancPluginValueRepresentation_UN: - return EVR_UN; + case HttpMethod_Post: + return OrthancPluginHttpMethod_Post; - case OrthancPluginValueRepresentation_US: - return EVR_US; + case HttpMethod_Put: + return OrthancPluginHttpMethod_Put; - case OrthancPluginValueRepresentation_UT: - return EVR_UT; + case HttpMethod_Delete: + return OrthancPluginHttpMethod_Delete; default: throw OrthancException(ErrorCode_ParameterOutOfRange); } } -#endif + + + ValueRepresentation Convert(OrthancPluginValueRepresentation vr) + { + switch (vr) + { + case OrthancPluginValueRepresentation_AE: + return ValueRepresentation_ApplicationEntity; + + case OrthancPluginValueRepresentation_AS: + return ValueRepresentation_AgeString; + + case OrthancPluginValueRepresentation_AT: + return ValueRepresentation_AttributeTag; + + case OrthancPluginValueRepresentation_CS: + return ValueRepresentation_CodeString; + + case OrthancPluginValueRepresentation_DA: + return ValueRepresentation_Date; + + case OrthancPluginValueRepresentation_DS: + return ValueRepresentation_DecimalString; + + case OrthancPluginValueRepresentation_DT: + return ValueRepresentation_DateTime; + + case OrthancPluginValueRepresentation_FD: + return ValueRepresentation_FloatingPointDouble; + + case OrthancPluginValueRepresentation_FL: + return ValueRepresentation_FloatingPointSingle; + + case OrthancPluginValueRepresentation_IS: + return ValueRepresentation_IntegerString; + + case OrthancPluginValueRepresentation_LO: + return ValueRepresentation_LongString; + + case OrthancPluginValueRepresentation_LT: + return ValueRepresentation_LongText; + + case OrthancPluginValueRepresentation_OB: + return ValueRepresentation_OtherByte; + + case OrthancPluginValueRepresentation_OF: + return ValueRepresentation_OtherFloat; + + case OrthancPluginValueRepresentation_OW: + return ValueRepresentation_OtherWord; + + case OrthancPluginValueRepresentation_PN: + return ValueRepresentation_PersonName; + + case OrthancPluginValueRepresentation_SH: + return ValueRepresentation_ShortString; + + case OrthancPluginValueRepresentation_SL: + return ValueRepresentation_SignedLong; + + case OrthancPluginValueRepresentation_SQ: + return ValueRepresentation_Sequence; + + case OrthancPluginValueRepresentation_SS: + return ValueRepresentation_SignedShort; + + case OrthancPluginValueRepresentation_ST: + return ValueRepresentation_ShortText; + + case OrthancPluginValueRepresentation_TM: + return ValueRepresentation_Time; + + case OrthancPluginValueRepresentation_UI: + return ValueRepresentation_UniqueIdentifier; + + case OrthancPluginValueRepresentation_UL: + return ValueRepresentation_UnsignedLong; + + case OrthancPluginValueRepresentation_UN: + return ValueRepresentation_Unknown; + + case OrthancPluginValueRepresentation_US: + return ValueRepresentation_UnsignedShort; + + case OrthancPluginValueRepresentation_UT: + return ValueRepresentation_UnlimitedText; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + + /* + Not supported as of DCMTK 3.6.0: + return ValueRepresentation_OtherDouble + return ValueRepresentation_OtherLong + return ValueRepresentation_UniversalResource + return ValueRepresentation_UnlimitedCharacters + */ + } + } + + + OrthancPluginValueRepresentation Convert(ValueRepresentation vr) + { + switch (vr) + { + case ValueRepresentation_ApplicationEntity: + return OrthancPluginValueRepresentation_AE; + + case ValueRepresentation_AgeString: + return OrthancPluginValueRepresentation_AS; + + case ValueRepresentation_AttributeTag: + return OrthancPluginValueRepresentation_AT; + + case ValueRepresentation_CodeString: + return OrthancPluginValueRepresentation_CS; + + case ValueRepresentation_Date: + return OrthancPluginValueRepresentation_DA; + + case ValueRepresentation_DecimalString: + return OrthancPluginValueRepresentation_DS; + + case ValueRepresentation_DateTime: + return OrthancPluginValueRepresentation_DT; + + case ValueRepresentation_FloatingPointDouble: + return OrthancPluginValueRepresentation_FD; + + case ValueRepresentation_FloatingPointSingle: + return OrthancPluginValueRepresentation_FL; + + case ValueRepresentation_IntegerString: + return OrthancPluginValueRepresentation_IS; + + case ValueRepresentation_LongString: + return OrthancPluginValueRepresentation_LO; + + case ValueRepresentation_LongText: + return OrthancPluginValueRepresentation_LT; + + case ValueRepresentation_OtherByte: + return OrthancPluginValueRepresentation_OB; + + case ValueRepresentation_OtherFloat: + return OrthancPluginValueRepresentation_OF; + + case ValueRepresentation_OtherWord: + return OrthancPluginValueRepresentation_OW; + + case ValueRepresentation_PersonName: + return OrthancPluginValueRepresentation_PN; + + case ValueRepresentation_ShortString: + return OrthancPluginValueRepresentation_SH; + + case ValueRepresentation_SignedLong: + return OrthancPluginValueRepresentation_SL; + + case ValueRepresentation_Sequence: + return OrthancPluginValueRepresentation_SQ; + + case ValueRepresentation_SignedShort: + return OrthancPluginValueRepresentation_SS; + + case ValueRepresentation_ShortText: + return OrthancPluginValueRepresentation_ST; + + case ValueRepresentation_Time: + return OrthancPluginValueRepresentation_TM; + + case ValueRepresentation_UniqueIdentifier: + return OrthancPluginValueRepresentation_UI; + + case ValueRepresentation_UnsignedLong: + return OrthancPluginValueRepresentation_UL; + + case ValueRepresentation_UnsignedShort: + return OrthancPluginValueRepresentation_US; + + case ValueRepresentation_UnlimitedText: + return OrthancPluginValueRepresentation_UT; + + case ValueRepresentation_Unknown: + return OrthancPluginValueRepresentation_UN; // Unknown + + // These VR are not supported as of DCMTK 3.6.0, so they are + // mapped to "UN" (unknown) VR in the plugins + case ValueRepresentation_OtherDouble: + case ValueRepresentation_OtherLong: + case ValueRepresentation_UniversalResource: + case ValueRepresentation_UnlimitedCharacters: + return OrthancPluginValueRepresentation_UN; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + OrthancPluginJobStepStatus Convert(JobStepCode step) + { + switch (step) + { + case JobStepCode_Success: + return OrthancPluginJobStepStatus_Success; + + case JobStepCode_Failure: + return OrthancPluginJobStepStatus_Failure; + + case JobStepCode_Continue: + return OrthancPluginJobStepStatus_Continue; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + JobStepCode Convert(OrthancPluginJobStepStatus step) + { + switch (step) + { + case OrthancPluginJobStepStatus_Success: + return JobStepCode_Success; + + case OrthancPluginJobStepStatus_Failure: + return JobStepCode_Failure; + + case OrthancPluginJobStepStatus_Continue: + return JobStepCode_Continue; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } } }
--- a/Plugins/Engine/PluginsEnumerations.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/PluginsEnumerations.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +33,11 @@ #pragma once -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 #include "../Include/orthanc/OrthancCPlugin.h" #include "../../OrthancServer/ServerEnumerations.h" -#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 -#include <dcmtk/dcmdata/dcvr.h> -#endif - namespace Orthanc { namespace Plugins @@ -65,9 +62,17 @@ IdentifierConstraintType Convert(OrthancPluginIdentifierConstraint constraint); -#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 - DcmEVR Convert(OrthancPluginValueRepresentation vr); -#endif + OrthancPluginInstanceOrigin Convert(RequestOrigin origin); + + OrthancPluginHttpMethod Convert(HttpMethod method); + + ValueRepresentation Convert(OrthancPluginValueRepresentation vr); + + OrthancPluginValueRepresentation Convert(ValueRepresentation vr); + + OrthancPluginJobStepStatus Convert(JobStepCode step); + + JobStepCode Convert(OrthancPluginJobStepStatus step); } }
--- a/Plugins/Engine/PluginsErrorDictionary.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/PluginsErrorDictionary.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ #include "../../OrthancServer/PrecompiledHeadersServer.h" #include "PluginsErrorDictionary.h" -#if ORTHANC_PLUGINS_ENABLED != 1 +#if ORTHANC_ENABLE_PLUGINS != 1 #error The plugin support is disabled #endif
--- a/Plugins/Engine/PluginsErrorDictionary.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/PluginsErrorDictionary.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,11 +33,11 @@ #pragma once -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 #include "../Include/orthanc/OrthancCPlugin.h" #include "../../Core/OrthancException.h" -#include "SharedLibrary.h" +#include "../../Core/SharedLibrary.h" #include <map> #include <string>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Engine/PluginsJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,189 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../../OrthancServer/PrecompiledHeadersServer.h" +#include "PluginsJob.h" + +#if ORTHANC_ENABLE_PLUGINS != 1 +#error The plugin support is disabled +#endif + + +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" + +#include <json/reader.h> +#include <cassert> + +namespace Orthanc +{ + PluginsJob::PluginsJob(const _OrthancPluginCreateJob& parameters) : + parameters_(parameters) + { + if (parameters_.job == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + if (parameters_.target == NULL || + parameters_.finalize == NULL || + parameters_.type == NULL || + parameters_.getProgress == NULL || + parameters_.getContent == NULL || + parameters_.getSerialized == NULL || + parameters_.step == NULL || + parameters_.stop == NULL || + parameters_.reset == NULL) + { + parameters_.finalize(parameters.job); + throw OrthancException(ErrorCode_NullPointer); + } + + type_.assign(parameters.type); + } + + PluginsJob::~PluginsJob() + { + assert(parameters_.job != NULL); + parameters_.finalize(parameters_.job); + } + + JobStepResult PluginsJob::Step() + { + OrthancPluginJobStepStatus status = parameters_.step(parameters_.job); + + switch (status) + { + case OrthancPluginJobStepStatus_Success: + return JobStepResult::Success(); + + case OrthancPluginJobStepStatus_Failure: + return JobStepResult::Failure(ErrorCode_Plugin); + + case OrthancPluginJobStepStatus_Continue: + return JobStepResult::Continue(); + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + void PluginsJob::Reset() + { + parameters_.reset(parameters_.job); + } + + void PluginsJob::Stop(JobStopReason reason) + { + switch (reason) + { + case JobStopReason_Success: + parameters_.stop(parameters_.job, OrthancPluginJobStopReason_Success); + break; + + case JobStopReason_Failure: + parameters_.stop(parameters_.job, OrthancPluginJobStopReason_Failure); + break; + + case JobStopReason_Canceled: + parameters_.stop(parameters_.job, OrthancPluginJobStopReason_Canceled); + break; + + case JobStopReason_Paused: + parameters_.stop(parameters_.job, OrthancPluginJobStopReason_Paused); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + float PluginsJob::GetProgress() + { + return parameters_.getProgress(parameters_.job); + } + + void PluginsJob::GetPublicContent(Json::Value& value) + { + const char* content = parameters_.getContent(parameters_.job); + + if (content == NULL) + { + value = Json::objectValue; + } + else + { + Json::Reader reader; + + if (!reader.parse(content, value) || + value.type() != Json::objectValue) + { + LOG(ERROR) << "A job plugin must provide a JSON object as its public content"; + throw OrthancException(ErrorCode_Plugin); + } + } + } + + bool PluginsJob::Serialize(Json::Value& value) + { + const char* serialized = parameters_.getSerialized(parameters_.job); + + if (serialized == NULL) + { + return false; + } + else + { + Json::Reader reader; + + if (!reader.parse(serialized, value) || + value.type() != Json::objectValue) + { + LOG(ERROR) << "A job plugin must provide a JSON object as its serialized content"; + throw OrthancException(ErrorCode_Plugin); + } + + + static const char* KEY_TYPE = "Type"; + + if (value.isMember(KEY_TYPE)) + { + LOG(ERROR) << "The \"Type\" field is for reserved use for serialized job"; + throw OrthancException(ErrorCode_Plugin); + } + + value[KEY_TYPE] = type_; + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Engine/PluginsJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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_PLUGINS == 1 + +#include "../../Core/JobsEngine/IJob.h" +#include "../Include/orthanc/OrthancCPlugin.h" + +namespace Orthanc +{ + class PluginsJob : public IJob + { + private: + _OrthancPluginCreateJob parameters_; + std::string type_; + + public: + PluginsJob(const _OrthancPluginCreateJob& parameters); + + virtual ~PluginsJob(); + + virtual void Start() + { + } + + virtual JobStepResult Step(); + + virtual void Reset(); + + virtual void Stop(JobStopReason reason); + + virtual float GetProgress(); + + virtual void GetJobType(std::string& target) + { + target = type_; + } + + virtual void GetPublicContent(Json::Value& value); + + virtual bool Serialize(Json::Value& value); + }; +} + +#endif
--- a/Plugins/Engine/PluginsManager.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/PluginsManager.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,14 +34,14 @@ #include "../../OrthancServer/PrecompiledHeadersServer.h" #include "PluginsManager.h" -#if ORTHANC_PLUGINS_ENABLED != 1 +#if ORTHANC_ENABLE_PLUGINS != 1 #error The plugin support is disabled #endif - -#include "../../Core/Toolbox.h" #include "../../Core/HttpServer/HttpOutput.h" #include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" +#include "../../Core/Toolbox.h" #include <cassert> #include <memory> @@ -48,7 +49,7 @@ #ifdef WIN32 #define PLUGIN_EXTENSION ".dll" -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) #define PLUGIN_EXTENSION ".so" #elif defined(__APPLE__) && defined(__MACH__) #define PLUGIN_EXTENSION ".dylib" @@ -191,7 +192,11 @@ catch (OrthancException& e) { // This service provider has failed - LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What(); + if (e.GetErrorCode() != ErrorCode_UnknownResource) // This error code is valid in plugins + { + LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What(); + } + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); } }
--- a/Plugins/Engine/PluginsManager.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Engine/PluginsManager.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +33,8 @@ #pragma once -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 -#include "SharedLibrary.h" #include "IPluginServiceProvider.h" #include <map>
--- a/Plugins/Engine/SharedLibrary.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../../OrthancServer/PrecompiledHeadersServer.h" -#include "SharedLibrary.h" - -#if ORTHANC_PLUGINS_ENABLED != 1 -#error The plugin support is disabled -#endif - - -#include "../../Core/Logging.h" -#include "../../Core/Toolbox.h" - -#include <boost/filesystem.hpp> - -#if defined(_WIN32) -#include <windows.h> -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) -#include <dlfcn.h> -#else -#error Support your platform here -#endif - -namespace Orthanc -{ - SharedLibrary::SharedLibrary(const std::string& path) : - path_(path), - handle_(NULL) - { -#if defined(_WIN32) - handle_ = ::LoadLibraryA(path_.c_str()); - if (handle_ == NULL) - { - LOG(ERROR) << "LoadLibrary(" << path_ << ") failed: Error " << ::GetLastError(); - throw OrthancException(ErrorCode_SharedLibrary); - } - -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) - handle_ = ::dlopen(path_.c_str(), RTLD_NOW); - if (handle_ == NULL) - { - std::string explanation; - const char *tmp = ::dlerror(); - if (tmp) - { - explanation = ": Error " + std::string(tmp); - } - - LOG(ERROR) << "dlopen(" << path_ << ") failed" << explanation; - throw OrthancException(ErrorCode_SharedLibrary); - } - -#else -#error Support your platform here -#endif - } - - SharedLibrary::~SharedLibrary() - { - if (handle_) - { -#if defined(_WIN32) - ::FreeLibrary((HMODULE)handle_); -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) - ::dlclose(handle_); -#else -#error Support your platform here -#endif - } - } - - - SharedLibrary::FunctionPointer SharedLibrary::GetFunctionInternal(const std::string& name) - { - if (!handle_) - { - throw OrthancException(ErrorCode_InternalError); - } - -#if defined(_WIN32) - return ::GetProcAddress((HMODULE)handle_, name.c_str()); -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) - return ::dlsym(handle_, name.c_str()); -#else -#error Support your platform here -#endif - } - - - SharedLibrary::FunctionPointer SharedLibrary::GetFunction(const std::string& name) - { - SharedLibrary::FunctionPointer result = GetFunctionInternal(name); - - if (result == NULL) - { - LOG(ERROR) << "Shared library does not expose function \"" << name << "\""; - throw OrthancException(ErrorCode_SharedLibrary); - } - else - { - return result; - } - } - - - bool SharedLibrary::HasFunction(const std::string& name) - { - return GetFunctionInternal(name) != NULL; - } -}
--- a/Plugins/Engine/SharedLibrary.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if ORTHANC_PLUGINS_ENABLED == 1 - -#include "../../Core/OrthancException.h" - -#include <boost/noncopyable.hpp> - -#if defined(_WIN32) -#include <windows.h> -#endif - -namespace Orthanc -{ - class SharedLibrary : public boost::noncopyable - { - public: -#if defined(_WIN32) - typedef FARPROC FunctionPointer; -#else - typedef void* FunctionPointer; -#endif - - private: - std::string path_; - void *handle_; - - FunctionPointer GetFunctionInternal(const std::string& name); - - public: - SharedLibrary(const std::string& path); - - ~SharedLibrary(); - - const std::string& GetPath() const - { - return path_; - } - - bool HasFunction(const std::string& name); - - FunctionPointer GetFunction(const std::string& name); - }; -} - -#endif
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Fri Oct 12 15:18:10 2018 +0200 @@ -4,8 +4,9 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -681,6 +682,18 @@ OrthancPluginResourceType resourceType, const OrthancPluginDicomTag* tag, OrthancPluginIdentifierConstraint constraint); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifierRange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint16_t group, + uint16_t element, + const char* start, + const char* end); } OrthancPluginDatabaseExtensions; /*<! @endcond */ @@ -694,13 +707,8 @@ } _OrthancPluginRegisterDatabaseBackend; /** - * Register a custom database back-end. + * Register a custom database back-end (for legacy plugins). * - * Instead of manually filling the OrthancPluginDatabaseBackend - * structure, you should instead implement a concrete C++ class - * deriving from ::OrthancPlugins::IDatabaseBackend, and register it - * using ::OrthancPlugins::DatabaseBackendAdapter::Register(). - * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param backend The callbacks of the custom database engine. * @param payload Pointer containing private information for the database engine. @@ -753,11 +761,6 @@ /** * Register a custom database back-end. * - * Instead of manually filling the OrthancPluginDatabaseBackendV2 - * structure, you should instead implement a concrete C++ class - * deriving from ::OrthancPlugins::IDatabaseBackend, and register it - * using ::OrthancPlugins::DatabaseBackendAdapter::Register(). - * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param backend The callbacks of the custom database engine. * @param payload Pointer containing private information for the database engine.
--- a/Plugins/Include/orthanc/OrthancCPlugin.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Fri Oct 12 15:18:10 2018 +0200 @@ -18,6 +18,12 @@ * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). + * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback(). + * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). + * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2(). + * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). * -# <tt>void OrthancPluginFinalize()</tt>: * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -49,6 +55,9 @@ * @defgroup Callbacks Callbacks * @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). + * * @defgroup Orthanc Orthanc * @brief Functions to access the content of the Orthanc server. **/ @@ -64,8 +73,9 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -107,9 +117,19 @@ #define ORTHANC_PLUGINS_API #endif -#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 0 -#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 9 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 5 +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 4 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 2 + + +#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 && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif @@ -183,7 +203,7 @@ OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, - OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */, OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, @@ -213,6 +233,10 @@ OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, + OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + OrthancPluginErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, + OrthancPluginErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, + OrthancPluginErrorCode_CanceledJob = 37 /*!< This job was canceled */, OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, @@ -232,8 +256,8 @@ OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, - OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, - OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is privileged or already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is privileged or already in use */, OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, @@ -270,6 +294,8 @@ OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, 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_INTERNAL = 0x7fffffff } OrthancPluginErrorCode; @@ -390,6 +416,13 @@ _OrthancPluginService_RegisterDictionaryTag = 20, _OrthancPluginService_DicomBufferToJson = 21, _OrthancPluginService_DicomInstanceToJson = 22, + _OrthancPluginService_CreateDicom = 23, + _OrthancPluginService_ComputeMd5 = 24, + _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, + _OrthancPluginService_CallHttpClient2 = 27, + _OrthancPluginService_GenerateUuid = 28, + _OrthancPluginService_RegisterPrivateDictionaryTag = 29, /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -397,6 +430,12 @@ _OrthancPluginService_RegisterStorageArea = 1002, _OrthancPluginService_RegisterOnChangeCallback = 1003, _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + _OrthancPluginService_RegisterWorklistCallback = 1005, + _OrthancPluginService_RegisterDecodeImageCallback = 1006, + _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007, + _OrthancPluginService_RegisterFindCallback = 1008, + _OrthancPluginService_RegisterMoveCallback = 1009, + _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -411,6 +450,7 @@ _OrthancPluginService_SendMultipartItem = 2009, _OrthancPluginService_SendHttpStatus = 2010, _OrthancPluginService_CompressAndAnswerImage = 2011, + _OrthancPluginService_SendMultipartItem2 = 2012, /* Access to the Orthanc database and API */ _OrthancPluginService_GetDicomForInstance = 3000, @@ -428,6 +468,7 @@ _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, _OrthancPluginService_RestApiPutAfterPlugins = 3013, _OrthancPluginService_ReconstructMainDicomTags = 3014, + _OrthancPluginService_RestApiGet2 = 3015, /* Access to DICOM instances */ _OrthancPluginService_GetInstanceRemoteAet = 4000, @@ -437,6 +478,7 @@ _OrthancPluginService_GetInstanceSimplifiedJson = 4004, _OrthancPluginService_HasInstanceMetadata = 4005, _OrthancPluginService_GetInstanceMetadata = 4006, + _OrthancPluginService_GetInstanceOrigin = 4007, /* Services for plugins implementing a database back-end */ _OrthancPluginService_RegisterDatabaseBackend = 5000, @@ -459,7 +501,40 @@ _OrthancPluginService_GetFontsCount = 6009, _OrthancPluginService_GetFontInfo = 6010, _OrthancPluginService_DrawText = 6011, - + _OrthancPluginService_CreateImage = 6012, + _OrthancPluginService_CreateImageAccessor = 6013, + _OrthancPluginService_DecodeDicomImage = 6014, + + /* Primitives for handling C-Find, C-Move and worklists */ + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, + _OrthancPluginService_FindAddAnswer = 7004, + _OrthancPluginService_FindMarkIncomplete = 7005, + _OrthancPluginService_GetFindQuerySize = 7006, + _OrthancPluginService_GetFindQueryTag = 7007, + _OrthancPluginService_GetFindQueryTagName = 7008, + _OrthancPluginService_GetFindQueryValue = 7009, + _OrthancPluginService_CreateFindMatcher = 7010, + _OrthancPluginService_FreeFindMatcher = 7011, + _OrthancPluginService_FindMatcherIsMatch = 7012, + + /* Primitives for accessing Orthanc Peers (new in 1.4.2) */ + _OrthancPluginService_GetPeers = 8000, + _OrthancPluginService_FreePeers = 8001, + _OrthancPluginService_GetPeersCount = 8003, + _OrthancPluginService_GetPeerName = 8004, + _OrthancPluginService_GetPeerUrl = 8005, + _OrthancPluginService_CallPeerApi = 8006, + _OrthancPluginService_GetPeerUserProperty = 8007, + + /* Primitives for handling jobs (new in 1.4.2) */ + _OrthancPluginService_CreateJob = 9000, + _OrthancPluginService_FreeJob = 9001, + _OrthancPluginService_SubmitJob = 9002, + _OrthancPluginService_RegisterJobsUnserializer = 9003, + _OrthancPluginService_INTERNAL = 0x7fffffff } _OrthancPluginService; @@ -523,6 +598,46 @@ OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + /** + * @brief Color image in RGB48 format. + * + * This format describes a color image. The pixels are stored in 6 + * consecutive bytes. The memory layout is RRGGBB. + **/ + OrthancPluginPixelFormat_RGB48 = 7, + + /** + * @brief Graylevel, unsigned 32bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * four bytes. + **/ + OrthancPluginPixelFormat_Grayscale32 = 8, + + /** + * @brief Graylevel, floating-point 32bpp image. + * + * The image is graylevel. Each pixel is floating-point and stored + * in four bytes. + **/ + OrthancPluginPixelFormat_Float32 = 9, + + /** + * @brief Color image in BGRA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is BGRA. + **/ + OrthancPluginPixelFormat_BGRA32 = 10, + + /** + * @brief Graylevel, unsigned 64bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * eight bytes. + **/ + OrthancPluginPixelFormat_Grayscale64 = 11, + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff } OrthancPluginPixelFormat; @@ -576,6 +691,10 @@ OrthancPluginChangeType_StableStudy = 9, /*!< Timeout: No new instance in this study */ OrthancPluginChangeType_OrthancStarted = 10, /*!< Orthanc has started */ OrthancPluginChangeType_OrthancStopped = 11, /*!< Orthanc is stopping */ + OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ + OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ + OrthancPluginChangeType_UpdatedPeers = 14, /*!< The list of Orthanc peers has changed */ + OrthancPluginChangeType_UpdatedModalities = 15, /*!< The list of DICOM modalities has changed */ _OrthancPluginChangeType_INTERNAL = 0x7fffffff } OrthancPluginChangeType; @@ -602,8 +721,9 @@ **/ typedef enum { - OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ - OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ _OrthancPluginImageFormat_INTERNAL = 0x7fffffff } OrthancPluginImageFormat; @@ -650,12 +770,13 @@ /** * The possible output formats for a DICOM-to-JSON conversion. * @ingroup Toolbox + * @see OrthancPluginDicomToJson() **/ typedef enum { OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ - OrthancPluginDicomToJsonFormat_Simple = 3, /*!< Human-readable JSON */ + OrthancPluginDicomToJsonFormat_Human = 3, /*!< Human-readable JSON */ _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff } OrthancPluginDicomToJsonFormat; @@ -668,6 +789,7 @@ **/ typedef enum { + OrthancPluginDicomToJsonFlags_None = 0, OrthancPluginDicomToJsonFlags_IncludeBinary = (1 << 0), /*!< Include the binary tags */ OrthancPluginDicomToJsonFlags_IncludePrivateTags = (1 << 1), /*!< Include the private tags */ OrthancPluginDicomToJsonFlags_IncludeUnknownTags = (1 << 2), /*!< Include the tags unknown by the dictionary */ @@ -680,20 +802,75 @@ /** + * Flags to the creation of a DICOM file. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + **/ + typedef enum + { + OrthancPluginCreateDicomFlags_None = 0, + OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ + OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ + + _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff + } OrthancPluginCreateDicomFlags; + + + /** * The constraints on the DICOM identifiers that must be supported * by the database plugins. **/ typedef enum { - OrthancPluginIdentifierConstraint_Equal, /*!< Equal */ - OrthancPluginIdentifierConstraint_SmallerOrEqual, /*!< Less or equal */ - OrthancPluginIdentifierConstraint_GreaterOrEqual, /*!< More or equal */ - OrthancPluginIdentifierConstraint_Wildcard, /*!< Case-sensitive wildcard matching (with * and ?) */ + OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ + OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff } OrthancPluginIdentifierConstraint; + /** + * The origin of a DICOM instance that has been received by Orthanc. + **/ + typedef enum + { + OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ + OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ + OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ + OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ + OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ + + _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff + } OrthancPluginInstanceOrigin; + + + /** + * The possible status for one single step of a job. + **/ + typedef enum + { + OrthancPluginJobStepStatus_Success = 1, /*!< The job has successfully executed all its steps */ + OrthancPluginJobStepStatus_Failure = 2, /*!< The job has failed while executing this step */ + OrthancPluginJobStepStatus_Continue = 3 /*!< The job has still data to process after this step */ + } OrthancPluginJobStepStatus; + + + /** + * Explains why the job should stop and release the resources it has + * allocated. This is especially important to disambiguate between + * the "paused" condition and the "final" conditions (success, + * failure, or canceled). + **/ + typedef enum + { + OrthancPluginJobStopReason_Success = 1, /*!< The job has succeeded */ + OrthancPluginJobStopReason_Paused = 2, /*!< The job was paused, and will be resumed later */ + OrthancPluginJobStopReason_Failure = 3, /*!< The job has failed, and might be resubmitted later */ + OrthancPluginJobStopReason_Canceled = 4 /*!< The job was canceled, and might be resubmitted later */ + } OrthancPluginJobStopReason; + /** * @brief A memory buffer allocated by the core system of Orthanc. @@ -750,6 +927,62 @@ /** + * @brief Opaque structure to an object that represents a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists. + * @ingroup DicomCallbacks + **/ + typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers; + + + + /** + * @brief Opaque structure to an object that can be used to check whether a DICOM instance matches a C-Find query. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindMatcher; + + + + /** + * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginPeers_t OrthancPluginPeers; + + + + /** + * @brief Opaque structure to a job to be executed by Orthanc. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginJob_t OrthancPluginJob; + + + + /** * @brief Signature of a callback function that answers to a REST request. * @ingroup Callbacks **/ @@ -782,6 +1015,18 @@ /** + * @brief Signature of a callback function to decode a DICOM instance as an image. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( + OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + + + + /** * @brief Signature of a function to free dynamic memory. **/ typedef void (*OrthancPluginFree) (void* buffer); @@ -845,6 +1090,337 @@ /** + * @brief Callback to handle the C-Find SCP requests for worklists. + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @param headersKeys The keys of the HTTP headers (always converted to low-case). + * @param headersValues The values of the HTTP headers. + * @return 0 if forbidden access, 1 if allowed access, -1 if error. + * @ingroup Callback + * @deprecated Please instead use OrthancPluginIncomingHttpRequestFilter2() + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues); + + + + /** + * @brief Callback to filter incoming HTTP requests received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives an HTTP/REST request, and that answers whether + * this request should be allowed. If the callback returns "0" + * ("false"), the server answers with HTTP status code 403 + * (Forbidden). + * + * @param method The HTTP method used by the request. + * @param uri The URI of interest. + * @param ip The IP address of the HTTP client. + * @param headersCount The number of HTTP headers. + * @param headersKeys The keys of the HTTP headers (always converted to low-case). + * @param headersValues The values of the HTTP headers. + * @param getArgumentsCount The number of GET arguments (only for the GET HTTP method). + * @param getArgumentsKeys The keys of the GET arguments (only for the GET HTTP method). + * @param getArgumentsValues The values of the GET arguments (only for the GET HTTP method). + * @return 0 if forbidden access, 1 if allowed access, -1 if error. + * @ingroup Callback + **/ + typedef int32_t (*OrthancPluginIncomingHttpRequestFilter2) ( + OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + uint32_t getArgumentsCount, + const char* const* getArgumentsKeys, + const char* const* getArgumentsValues); + + + + /** + * @brief Callback to handle incoming C-Find SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Find SCP request not concerning modality + * worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) ( + OrthancPluginFindAnswers* answers, + const OrthancPluginFindQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to handle incoming C-Move SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Move SCP request. The callback receives the + * type of the resource of interest (study, series, instance...) + * together with the DICOM tags containing its identifiers. In turn, + * the plugin must create a driver object that will be responsible + * for driving the successive move suboperations. + * + * @param resourceType The type of the resource of interest. Note + * that this might be set to ResourceType_None if the + * QueryRetrieveLevel (0008,0052) tag was not provided by the + * issuer (i.e. the originator modality). + * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL. + * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL. + * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL. + * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL. + * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL. + * @param originatorAet The Application Entity Title (AET) of the + * modality from which the request originates. + * @param sourceAet The Application Entity Title (AET) of the + * modality that should send its DICOM files to another modality. + * @param targetAet The Application Entity Title (AET) of the + * modality that should receive the DICOM files. + * @param originatorId The Message ID issued by the originator modality, + * as found in tag (0000,0110) of the DICOM query emitted by the issuer. + * + * @return The NULL value if the plugin cannot deal with this query, + * or a pointer to the driver object that is responsible for + * handling the successive move suboperations. + * + * @note If targetAet equals sourceAet, this is actually a query/retrieve operation. + * @ingroup DicomCallbacks + **/ + typedef void* (*OrthancPluginMoveCallback) ( + OrthancPluginResourceType resourceType, + const char* patientId, + const char* accessionNumber, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + const char* originatorAet, + const char* sourceAet, + const char* targetAet, + uint16_t originatorId); + + + /** + * @brief Callback to read the size of a C-Move driver. + * + * Signature of a callback function that returns the number of + * C-Move suboperations that are to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return The number of suboperations. + * @ingroup DicomCallbacks + **/ + typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver); + + + /** + * @brief Callback to apply one C-Move suboperation. + * + * Signature of a callback function that applies the next C-Move + * suboperation that os to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return 0 if success, or the error code if failure. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver); + + + /** + * @brief Callback to free one C-Move driver. + * + * Signature of a callback function that releases the resources + * allocated by the given C-Move driver. This driver is the return + * value of a previous call to the OrthancPluginMoveCallback() + * callback. + * + * @param moveDriver The C-Move driver of interest. + * @ingroup DicomCallbacks + **/ + typedef void (*OrthancPluginFreeMove) (void* moveDriver); + + + /** + * @brief Callback to finalize one custom job. + * + * Signature of a callback function that releases all the resources + * allocated by the given job. This job is the argument provided to + * OrthancPluginCreateJob(). + * + * @param job The job of interest. + * @ingroup Toolbox + **/ + typedef void (*OrthancPluginJobFinalize) (void* job); + + + /** + * @brief Callback to check the progress of one custom job. + * + * Signature of a callback function that returns the progress of the + * job. + * + * @param job The job of interest. + * @return The progress, as a floating-point number ranging from 0 to 1. + * @ingroup Toolbox + **/ + typedef float (*OrthancPluginJobGetProgress) (void* job); + + + /** + * @brief Callback to retrieve the content of one custom job. + * + * Signature of a callback function that returns human-readable + * statistics about the job. This statistics must be formatted as a + * JSON object. This information is notably displayed in the "Jobs" + * tab of "Orthanc Explorer". + * + * @param job The job of interest. + * @return The statistics, as a JSON object encoded as a string. + * @ingroup Toolbox + **/ + typedef const char* (*OrthancPluginJobGetContent) (void* job); + + + /** + * @brief Callback to serialize one custom job. + * + * Signature of a callback function that returns a serialized + * version of the job, formatted as a JSON object. This + * serialization is stored in the Orthanc database, and is used to + * reload the job on the restart of Orthanc. The "unserialization" + * callback (with OrthancPluginJobsUnserializer signature) will + * receive this serialized object. + * + * @param job The job of interest. + * @return The serialized job, as a JSON object encoded as a string. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Toolbox + **/ + typedef const char* (*OrthancPluginJobGetSerialized) (void* job); + + + /** + * @brief Callback to execute one step of a custom job. + * + * Signature of a callback function that executes one step in the + * job. The jobs engine of Orthanc will make successive calls to + * this method, as long as it returns + * OrthancPluginJobStepStatus_Continue. + * + * @param job The job of interest. + * @return The status of execution. + * @ingroup Toolbox + **/ + typedef OrthancPluginJobStepStatus (*OrthancPluginJobStep) (void* job); + + + /** + * @brief Callback executed once one custom job leaves the "running" state. + * + * Signature of a callback function that is invoked once a job + * leaves the "running" state. This can happen if the previous call + * to OrthancPluginJobStep has failed/succeeded, if the host Orthanc + * server is being stopped, or if the user manually tags the job as + * paused/canceled. This callback allows the plugin to free + * resources allocated for running this custom job (e.g. to stop + * threads, or to remove temporary files). + * + * Note that handling pauses might involves a specific treatment + * (such a stopping threads, but keeping temporary files on the + * disk). This "paused" situation can be checked by looking at the + * "reason" parameter. + * + * @param job The job of interest. + * @param reason The reason for leaving the "running" state. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobStop) (void* job, + OrthancPluginJobStopReason reason); + + + /** + * @brief Callback executed once one stopped custom job is started again. + * + * Signature of a callback function that is invoked once a job + * leaves the "failure/canceled" state, to be started again. This + * function will typically reset the progress to zero. Note that + * before being actually executed, the job would first be tagged as + * "pending" in the Orthanc jobs engine. + * + * @param job The job of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Toolbox + **/ + typedef OrthancPluginErrorCode (*OrthancPluginJobReset) (void* job); + + + /** + * @brief Callback executed to unserialized a custom job. + * + * Signature of a callback function that unserializes a job that was + * saved in the Orthanc database. + * + * @param jobType The type of the job, as provided to OrthancPluginCreateJob(). + * @param serialized The serialization of the job, as provided by OrthancPluginJobGetSerialized. + * @return The unserialized job (as created by OrthancPluginCreateJob()), or NULL + * if this unserializer cannot handle this job type. + * @see OrthancPluginRegisterJobsUnserializer() + * @ingroup Callbacks + **/ + typedef OrthancPluginJob* (*OrthancPluginJobsUnserializer) (const char* jobType, + const char* serialized); + + + + /** * @brief Data structure that contains information about the Orthanc core. **/ typedef struct _OrthancPluginContext_t @@ -858,6 +1434,20 @@ } OrthancPluginContext; + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + /** * @brief Free a string. @@ -879,20 +1469,29 @@ /** - * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * @brief Check that the version of the hosting Orthanc is above a given version. * - * This function checks whether the version of this C header is - * compatible with the current version of Orthanc. The result of - * this function should always be checked in the - * OrthancPluginInitialize() entry point of the plugin. + * This function checks whether the version of the Orthanc server + * running this plugin, is above the given version. Contrarily to + * OrthancPluginCheckVersion(), it is up to the developer of the + * plugin to make sure that all the Orthanc SDK services called by + * the plugin are actually implemented in the given version of + * Orthanc. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param expectedMajor Expected major version. + * @param expectedMinor Expected minor version. + * @param expectedRevision Expected revision. * @return 1 if and only if the versions are compatible. If the * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersion * @ingroup Callbacks **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( - OrthancPluginContext* context) + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersionAdvanced( + OrthancPluginContext* context, + int expectedMajor, + int expectedMinor, + int expectedRevision) { int major, minor, revision; @@ -909,7 +1508,10 @@ sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || - sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint)) + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || + sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) || + sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus)) { /* Mismatch in the size of the enumerations */ return 0; @@ -935,31 +1537,31 @@ /* Check the major number of the version */ - if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + if (major > expectedMajor) { return 1; } - if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + if (major < expectedMajor) { return 0; } /* Check the minor number of the version */ - if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + if (minor > expectedMinor) { return 1; } - if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + if (minor < expectedMinor) { return 0; } /* Check the revision number of the version */ - if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) + if (revision >= expectedRevision) { return 1; } @@ -971,6 +1573,33 @@ /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of the Orthanc server + * running this plugin, is above the version of the current Orthanc + * SDK header. This guarantees that the plugin is compatible with + * the hosting Orthanc (i.e. it will not call unavailable services). + * The result of this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @see OrthancPluginCheckVersionAdvanced + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + return OrthancPluginCheckVersionAdvanced( + context, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + } + + + /** * @brief Free a memory buffer. * * Free a memory buffer that was allocated by the core system of Orthanc. @@ -1244,7 +1873,7 @@ * file is stored into a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param instanceId The Orthanc identifier of the DICOM instance of interest. * @return 0 if success, or the error code if failure. * @ingroup Orthanc @@ -1275,9 +1904,10 @@ * the query is stored into a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param uri The URI in the built-in Orthanc API. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiGetAfterPlugins * @ingroup Orthanc **/ @@ -1304,9 +1934,10 @@ * query is stored into a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param uri The URI in the built-in Orthanc API. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiGet * @ingroup Orthanc **/ @@ -1338,11 +1969,12 @@ * the query is stored into a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param uri The URI in the built-in Orthanc API. * @param body The body of the POST request. * @param bodySize The size of the body. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiPostAfterPlugins * @ingroup Orthanc **/ @@ -1372,11 +2004,12 @@ * query is stored into a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param uri The URI in the built-in Orthanc API. * @param body The body of the POST request. * @param bodySize The size of the body. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiPost * @ingroup Orthanc **/ @@ -1405,6 +2038,7 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param uri The URI to delete in the built-in Orthanc API. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiDeleteAfterPlugins * @ingroup Orthanc **/ @@ -1427,6 +2061,7 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param uri The URI to delete in the built-in Orthanc API. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiDelete * @ingroup Orthanc **/ @@ -1446,11 +2081,12 @@ * the query is stored into a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param uri The URI in the built-in Orthanc API. * @param body The body of the PUT request. * @param bodySize The size of the body. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiPutAfterPlugins * @ingroup Orthanc **/ @@ -1481,11 +2117,12 @@ * query is stored into a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param uri The URI in the built-in Orthanc API. * @param body The body of the PUT request. * @param bodySize The size of the body. * @return 0 if success, or the error code if failure. + * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource. * @see OrthancPluginRestApiPut * @ingroup Orthanc **/ @@ -1857,11 +2494,12 @@ typedef struct { - char** resultStringToFree; - const char** resultString; - int64_t* resultInt64; - const char* key; - OrthancPluginDicomInstance* instance; + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ } _OrthancPluginAccessDicomInstance; @@ -2274,7 +2912,7 @@ * Orthanc, you should make these calls in a separate thread (with * the events passing through a message queue). Otherwise, this * could result in deadlocks in the presence of other plugins or Lua - * script. + * scripts. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param callback The callback function. @@ -2524,7 +3162,6 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @return The version. * @ingroup Callbacks - * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() **/ ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( OrthancPluginContext* context) @@ -2597,7 +3234,7 @@ * @param subType The sub-type of the multipart answer ("mixed" or "related"). * @param contentType The MIME type of the items in the multipart answer. * @return 0 if success, or the error code if failure. - * @see OrthancPluginSendMultipartItem() + * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() * @ingroup REST **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( @@ -2626,6 +3263,7 @@ * @param answerSize Number of bytes of the item. * @return 0 if success, or the error code if failure (this notably happens * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem2() * @ingroup REST **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( @@ -2661,7 +3299,7 @@ * version of the zlib library that is used by the Orthanc core. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param source The source buffer. * @param size The size in bytes of the source buffer. * @param compression The compression algorithm. @@ -2703,7 +3341,7 @@ * a newly allocated memory buffer. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param path The path of the file to be read. * @return 0 if success, or the error code if failure. **/ @@ -2844,7 +3482,7 @@ const OrthancPluginImage* image; uint32_t* resultUint32; OrthancPluginPixelFormat* resultPixelFormat; - const void** resultBuffer; + void** resultBuffer; } _OrthancPluginGetImageInfo; @@ -2993,11 +3631,11 @@ * @return The pointer. * @ingroup Images **/ - ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetImageBuffer( + ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( OrthancPluginContext* context, const OrthancPluginImage* image) { - const void* target = NULL; + void* target = NULL; _OrthancPluginGetImageInfo params; memset(¶ms, 0, sizeof(params)); @@ -3261,7 +3899,7 @@ * Orthanc instance that hosts this plugin. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param url The URL of interest. * @param username The username (can be <tt>NULL</tt> if no password protection). * @param password The password (can be <tt>NULL</tt> if no password protection). @@ -3296,7 +3934,7 @@ * the Orthanc instance that hosts this plugin. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param url The URL of interest. * @param body The content of the body of the request. * @param bodySize The size of the body of the request. @@ -3337,7 +3975,7 @@ * Orthanc instance that hosts this plugin. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). * @param url The URL of interest. * @param body The content of the body of the request. * @param bodySize The size of the body of the request. @@ -3790,9 +4428,9 @@ /** * @brief Register a new tag into the DICOM dictionary. * - * This function declares a new tag in the dictionary of DICOM tags - * that is known to Orthanc. This function should be used in the - * OrthancPluginInitialize() callback. + * This function declares a new public tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param group The group of the tag. @@ -3803,6 +4441,7 @@ * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means * an arbitrary multiplicity ("<tt>n</tt>"). * @return 0 if success, other value if error. + * @see OrthancPluginRegisterPrivateDictionaryTag() * @ingroup Toolbox **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( @@ -3827,6 +4466,60 @@ + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + const char* privateCreator; + } _OrthancPluginRegisterPrivateDictionaryTag; + + /** + * @brief Register a new private tag into the DICOM dictionary. + * + * This function declares a new private tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("<tt>n</tt>"). + * @param privateCreator The private creator of this private tag. + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterPrivateDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity, + const char* privateCreator) + { + _OrthancPluginRegisterPrivateDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, ¶ms); + } + + typedef struct { @@ -3840,9 +4533,7 @@ * This function requests the Orthanc core to reconstruct the main * DICOM tags of all the resources of the given type. This function * can only be used as a part of the upgrade of a custom database - * back-end - * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A - * database transaction will be automatically setup. + * back-end. A database transaction will be automatically setup. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param storageArea The storage area. @@ -3867,7 +4558,7 @@ { char** result; const char* instanceId; - const char* buffer; + const void* buffer; uint32_t size; OrthancPluginDicomToJsonFormat format; OrthancPluginDicomToJsonFlags flags; @@ -3886,7 +4577,7 @@ * @param buffer The memory buffer containing the DICOM file. * @param size The size of the memory buffer. * @param format The output format. - * @param flags The output flags. + * @param flags Flags governing the output. * @param maxStringLength The maximum length of a field. Too long fields will * be output as "null". The 0 value means no maximum length. * @return The NULL value if the case of an error, or the JSON @@ -3896,7 +4587,7 @@ **/ ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson( OrthancPluginContext* context, - const char* buffer, + const void* buffer, uint32_t size, OrthancPluginDicomToJsonFormat format, OrthancPluginDicomToJsonFlags flags, @@ -3935,7 +4626,7 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param instanceId The Orthanc identifier of the instance. * @param format The output format. - * @param flags The output flags. + * @param flags Flags governing the output. * @param maxStringLength The maximum length of a field. Too long fields will * be output as "null". The 0 value means no maximum length. * @return The NULL value if the case of an error, or the JSON @@ -3972,6 +4663,1759 @@ } + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + int32_t afterPlugins; + } _OrthancPluginRestApiGet2; + + /** + * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. + * + * Make a GET call to the Orthanc REST API with extended + * parameters. The result to the query is stored into a newly + * allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header). + * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header). + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + int32_t afterPlugins) + { + _OrthancPluginRestApiGet2 params; + params.target = target; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistCallback callback; + } _OrthancPluginWorklistCallback; + + /** + * @brief Register a callback to handle modality worklists requests. + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( + OrthancPluginContext* context, + OrthancPluginWorklistCallback callback) + { + _OrthancPluginWorklistCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistAnswers* answers; + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + } _OrthancPluginWorklistAnswersOperation; + + /** + * @brief Add one answer to some modality worklist request. + * + * This function adds one worklist (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request against + * modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = query; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of worklist answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request against modality + * worklists. This must be used if canceling the handling of a + * request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = NULL; + params.dicom = NULL; + params.size = 0; + + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); + } + + + typedef struct + { + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + int32_t* isMatch; + OrthancPluginMemoryBuffer* target; + } _OrthancPluginWorklistQueryOperation; + + /** + * @brief Test whether a worklist matches the query. + * + * This function checks whether one worklist (encoded as a DICOM + * file) matches the C-Find SCP query against modality + * worklists. This function must be called before adding the + * worklist as an answer through OrthancPluginWorklistAddAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 1 if the worklist matches the query, 0 otherwise. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( + OrthancPluginContext* context, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + params.target = NULL; + + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + /** + * @brief Retrieve the worklist query as a DICOM file. + * + * This function retrieves the DICOM file that underlies a C-Find + * SCP query against modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param query The worklist query, as received by the callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginWorklistQuery* query) + { + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = NULL; + params.size = 0; + params.isMatch = NULL; + params.target = target; + + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); + } + + + /** + * @brief Get the origin of a DICOM file. + * + * This function returns the origin of a DICOM instance that has been received by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The origin of the instance. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + OrthancPluginInstanceOrigin origin; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultOrigin = &origin; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return OrthancPluginInstanceOrigin_Unknown; + } + else + { + return origin; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* json; + const OrthancPluginImage* pixelData; + OrthancPluginCreateDicomFlags flags; + } _OrthancPluginCreateDicom; + + /** + * @brief Create a DICOM instance from a JSON string and an image. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginDicomBufferToJson + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags) + { + _OrthancPluginCreateDicom params; + params.target = target; + params.json = json; + params.pixelData = pixelData; + params.flags = flags; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); + } + + + typedef struct + { + OrthancPluginDecodeImageCallback callback; + } _OrthancPluginDecodeImageCallback; + + /** + * @brief Register a callback to handle the decoding of DICOM images. + * + * This function registers a custom callback to the decoding of + * DICOM images, replacing the built-in decoder of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( + OrthancPluginContext* context, + OrthancPluginDecodeImageCallback callback) + { + _OrthancPluginDecodeImageCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + void* buffer; + const void* constBuffer; + uint32_t bufferSize; + uint32_t frameIndex; + } _OrthancPluginCreateImage; + + + /** + * @brief Create an image. + * + * This function creates an image of given size and format. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + + if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Create an image pointing to a memory buffer. + * + * This function creates an image whose content points to a memory + * buffer managed by the plugin. Note that the buffer is directly + * accessed, no memory is allocated and no data is copied. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + + if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is stored + * in a memory buffer. This function will give the same result as + * OrthancPluginUncompressImage() for single-frame DICOM images. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer Pointer to a memory buffer containing the DICOM image. + * @param bufferSize Size of the memory buffer containing the DICOM image. + * @param frameIndex The index of the frame of interest in a multi-frame image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( + OrthancPluginContext* context, + const void* buffer, + uint32_t bufferSize, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.constBuffer = buffer; + params.bufferSize = bufferSize; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** result; + const void* buffer; + uint32_t size; + } _OrthancPluginComputeHash; + + /** + * @brief Compute an MD5 hash. + * + * This functions computes the MD5 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Compute a SHA-1 hash. + * + * This functions computes the SHA-1 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + } _OrthancPluginSendMultipartItem2; + + /** + * @brief Send an item as a part of some HTTP multipart answer, with custom headers. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to + * OrthancPluginSendMultipartItem(), this function will set HTTP header associated + * with the item. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) + { + _OrthancPluginSendMultipartItem2 params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter callback; + } _OrthancPluginIncomingHttpRequestFilter; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + * @deprecated Please instead use OrthancPluginRegisterIncomingHttpRequestFilter2() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter callback) + { + _OrthancPluginIncomingHttpRequestFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + const char* body; + uint32_t bodySize; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginCallHttpClient2; + + + + /** + * @brief Issue a HTTP call with full flexibility. + * + * Make a HTTP call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. The HTTP request + * will be done accordingly to the global configuration of Orthanc + * (in particular, the options "HttpProxy", "HttpTimeout", + * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be + * taken into account). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param method HTTP method to be used. + * @param url The URL of interest. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header). + * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header). + * @param username The username (can be <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> if no password protection). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @param certificateFile Path to the client certificate for HTTPS, in PEM format + * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS). + * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format + * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS). + * @param certificateKeyPassword Password to unlock the key of the client certificate + * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS). + * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCallPeerApi() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpClient( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const char* body, + uint32_t bodySize, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginCallHttpClient2 params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.method = method; + params.url = url; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + params.timeout = timeout; + params.certificateFile = certificateFile; + params.certificateKeyFile = certificateKeyFile; + params.certificateKeyPassword = certificateKeyPassword; + params.pkcs11 = pkcs11; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, ¶ms); + } + + + /** + * @brief Generate an UUID. + * + * Generate a random GUID/UUID (globally unique identifier). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the UUID. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid( + OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginFindCallback callback; + } _OrthancPluginFindCallback; + + /** + * @brief Register a callback to handle C-Find requests. + * + * This function registers a callback to handle C-Find SCP requests + * that are not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback( + OrthancPluginContext* context, + OrthancPluginFindCallback callback) + { + _OrthancPluginFindCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, ¶ms); + } + + + typedef struct + { + OrthancPluginFindAnswers *answers; + const OrthancPluginFindQuery *query; + const void *dicom; + uint32_t size; + uint32_t index; + uint32_t *resultUint32; + uint16_t *resultGroup; + uint16_t *resultElement; + char **resultString; + } _OrthancPluginFindOperation; + + /** + * @brief Add one answer to some C-Find request. + * + * This function adds one answer (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request that is + * not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param dicom The answer to be added, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + * @see OrthancPluginCreateDicom() + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindAddAnswer( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers, + const void* dicom, + uint32_t size) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of C-Find answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request that is not related to + * modality worklists. This must be used if canceling the handling + * of a request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginFindMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginFindAnswers* answers) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.answers = answers; + + return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, ¶ms); + } + + + + /** + * @brief Get the number of tags in a C-Find query. + * + * This function returns the number of tags that are contained in + * the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @return The number of tags. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFindQuerySize( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query) + { + uint32_t count = 0; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + /** + * @brief Get one tag in a C-Find query. + * + * This function returns the group and the element of one DICOM tag + * in the given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag (output). + * @param element The element of the tag (output). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetFindQueryTag( + OrthancPluginContext* context, + uint16_t* group, + uint16_t* element, + const OrthancPluginFindQuery* query, + uint32_t index) + { + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultGroup = group; + params.resultElement = element; + + return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, ¶ms); + } + + + /** + * @brief Get the symbolic name of one tag in a C-Find query. + * + * This function returns the symbolic name of one DICOM tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the name of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryTagName( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the value associated with one tag in a C-Find query. + * + * This function returns the value associated with one tag in the + * given C-Find query. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find query. + * @param index The index of the tag of interest. + * @return The NULL value in case of error, or a string containing the value of the tag. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetFindQueryValue( + OrthancPluginContext* context, + const OrthancPluginFindQuery* query, + uint32_t index) + { + char* result; + + _OrthancPluginFindOperation params; + memset(¶ms, 0, sizeof(params)); + params.query = query; + params.index = index; + params.resultString = &result; + + if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + + typedef struct + { + OrthancPluginMoveCallback callback; + OrthancPluginGetMoveSize getMoveSize; + OrthancPluginApplyMove applyMove; + OrthancPluginFreeMove freeMove; + } _OrthancPluginMoveCallback; + + /** + * @brief Register a callback to handle C-Move requests. + * + * This function registers a callback to handle C-Move SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The main callback. + * @param getMoveSize Callback to read the number of C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperations. + * @param freeMove Callback to free the C-Move driver. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback( + OrthancPluginContext* context, + OrthancPluginMoveCallback callback, + OrthancPluginGetMoveSize getMoveSize, + OrthancPluginApplyMove applyMove, + OrthancPluginFreeMove freeMove) + { + _OrthancPluginMoveCallback params; + params.callback = callback; + params.getMoveSize = getMoveSize; + params.applyMove = applyMove; + params.freeMove = freeMove; + + return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginFindMatcher** target; + const void* query; + uint32_t size; + } _OrthancPluginCreateFindMatcher; + + + /** + * @brief Create a C-Find matcher. + * + * This function creates a "matcher" object that can be used to + * check whether a DICOM instance matches a C-Find query. The C-Find + * query must be expressed as a DICOM buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The C-Find DICOM query. + * @param size The size of the DICOM query. + * @return The newly allocated matcher. It must be freed with OrthancPluginFreeFindMatcher(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginFindMatcher* OrthancPluginCreateFindMatcher( + OrthancPluginContext* context, + const void* query, + uint32_t size) + { + OrthancPluginFindMatcher* target = NULL; + + _OrthancPluginCreateFindMatcher params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.query = query; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_CreateFindMatcher, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginFindMatcher* matcher; + } _OrthancPluginFreeFindMatcher; + + /** + * @brief Free a C-Find matcher. + * + * This function frees a matcher that was created using OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeFindMatcher( + OrthancPluginContext* context, + OrthancPluginFindMatcher* matcher) + { + _OrthancPluginFreeFindMatcher params; + params.matcher = matcher; + + context->InvokeService(context, _OrthancPluginService_FreeFindMatcher, ¶ms); + } + + + typedef struct + { + const OrthancPluginFindMatcher* matcher; + const void* dicom; + uint32_t size; + int32_t* isMatch; + } _OrthancPluginFindMatcherIsMatch; + + /** + * @brief Test whether a DICOM instance matches a C-Find query. + * + * This function checks whether one DICOM instance matches C-Find + * matcher that was previously allocated using + * OrthancPluginCreateFindMatcher(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param matcher The matcher of interest. + * @param dicom The DICOM instance to be matched. + * @param size The size of the DICOM instance. + * @return 1 if the DICOM instance matches the query, 0 otherwise. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginFindMatcherIsMatch( + OrthancPluginContext* context, + const OrthancPluginFindMatcher* matcher, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginFindMatcherIsMatch params; + params.matcher = matcher; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + + if (context->InvokeService(context, _OrthancPluginService_FindMatcherIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + typedef struct + { + OrthancPluginIncomingHttpRequestFilter2 callback; + } _OrthancPluginIncomingHttpRequestFilter2; + + /** + * @brief Register a callback to filter incoming HTTP requests. + * + * This function registers a custom callback to filter incoming HTTP/REST + * requests received by the HTTP server of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter2( + OrthancPluginContext* context, + OrthancPluginIncomingHttpRequestFilter2 callback) + { + _OrthancPluginIncomingHttpRequestFilter2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, ¶ms); + } + + + + typedef struct + { + OrthancPluginPeers** peers; + } _OrthancPluginGetPeers; + + /** + * @brief Return the list of available Orthanc peers. + * + * This function returns the parameters of the Orthanc peers that are known to + * the Orthanc server hosting the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL if error, or a newly allocated opaque data structure containing the peers. + * This structure must be freed with OrthancPluginFreePeers(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers( + OrthancPluginContext* context) + { + OrthancPluginPeers* peers = NULL; + + _OrthancPluginGetPeers params; + memset(¶ms, 0, sizeof(params)); + params.peers = &peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeers, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return peers; + } + } + + + typedef struct + { + OrthancPluginPeers* peers; + } _OrthancPluginFreePeers; + + /** + * @brief Free the list of available Orthanc peers. + * + * This function frees the data structure returned by OrthancPluginGetPeers(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreePeers( + OrthancPluginContext* context, + OrthancPluginPeers* peers) + { + _OrthancPluginFreePeers params; + params.peers = peers; + + context->InvokeService(context, _OrthancPluginService_FreePeers, ¶ms); + } + + + typedef struct + { + uint32_t* target; + const OrthancPluginPeers* peers; + } _OrthancPluginGetPeersCount; + + /** + * @brief Get the number of Orthanc peers. + * + * This function returns the number of Orthanc peers. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @result The number of peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount( + OrthancPluginContext* context, + const OrthancPluginPeers* peers) + { + uint32_t target = 0; + + _OrthancPluginGetPeersCount params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return target; + } + } + + + typedef struct + { + const char** target; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + const char* userProperty; + } _OrthancPluginGetPeerProperty; + + /** + * @brief Get the symbolic name of an Orthanc peer. + * + * This function returns the symbolic name of the Orthanc peer, + * which corresponds to the key of the "OrthancPeers" configuration + * option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The symbolic name, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Get the base URL of an Orthanc peer. + * + * This function returns the base URL to the REST API of some Orthanc peer. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The URL, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Get some user-defined property of an Orthanc peer. + * + * This function returns some user-defined property of some Orthanc + * peer. An user-defined property is a property that is associated + * with the peer in the Orthanc configuration file, but that is not + * recognized by the Orthanc core. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param userProperty The user property of interest. + * @result The value of the user property, or NULL if it is not defined. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUserProperty( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + const char* userProperty) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + params.userProperty = userProperty; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUserProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* No such user property */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t additionalHeadersCount; + const char* const* additionalHeadersKeys; + const char* const* additionalHeadersValues; + const char* body; + uint32_t bodySize; + uint32_t timeout; + } _OrthancPluginCallPeerApi; + + /** + * @brief Call the REST API of an Orthanc peer. + * + * Make a REST call to the given URI in the REST API of a remote + * Orthanc peer. The result to the query is stored into a newly + * allocated memory buffer. The HTTP request will be done according + * to the "OrthancPeers" configuration option of Orthanc. + * + * This function is thread-safe: Several threads sharing the same + * OrthancPluginPeers object can simultaneously call this function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param method HTTP method to be used. + * @param uri The URI of interest in the REST API. + * @param additionalHeadersCount The number of HTTP headers to be added to the + * HTTP headers provided in the global configuration of Orthanc. + * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header). + * @param additionalHeadersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallPeerApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t additionalHeadersCount, + const char* const* additionalHeadersKeys, + const char* const* additionalHeadersValues, + const char* body, + uint32_t bodySize, + uint32_t timeout) + { + _OrthancPluginCallPeerApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.peers = peers; + params.peerIndex = peerIndex; + params.method = method; + params.uri = uri; + params.additionalHeadersCount = additionalHeadersCount; + params.additionalHeadersKeys = additionalHeadersKeys; + params.additionalHeadersValues = additionalHeadersValues; + params.body = body; + params.bodySize = bodySize; + params.timeout = timeout; + + return context->InvokeService(context, _OrthancPluginService_CallPeerApi, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginJob** target; + void *job; + OrthancPluginJobFinalize finalize; + const char *type; + OrthancPluginJobGetProgress getProgress; + OrthancPluginJobGetContent getContent; + OrthancPluginJobGetSerialized getSerialized; + OrthancPluginJobStep step; + OrthancPluginJobStop stop; + OrthancPluginJobReset reset; + } _OrthancPluginCreateJob; + + /** + * @brief Create a custom job. + * + * This function creates a custom job to be run by the jobs engine + * of Orthanc. + * + * Orthanc starts one dedicated thread per custom job that is + * running. It is guaranteed that all the callbacks will only be + * called from this single dedicated thread, in mutual exclusion: As + * a consequence, it is *not* mandatory to protect the various + * callbacks by mutexes. + * + * The custom job can nonetheless launch its own processing threads + * on the first call to the "step()" callback, and stop them once + * the "stop()" callback is called. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job to be executed. + * @param finalize The finalization callback. + * @param type The type of the job, provided to the job unserializer. + * See OrthancPluginRegisterJobsUnserializer(). + * @param getProgress The progress callback. + * @param getContent The content callback. + * @param getSerialized The serialization callback. + * @param step The callback to execute the individual steps of the job. + * @param stop The callback that is invoked once the job leaves the "running" state. + * @param reset The callback that is invoked if a stopped job is started again. + * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(), + * as long as it is not submitted with OrthancPluginSubmitJob(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob( + OrthancPluginContext *context, + void *job, + OrthancPluginJobFinalize finalize, + const char *type, + OrthancPluginJobGetProgress getProgress, + OrthancPluginJobGetContent getContent, + OrthancPluginJobGetSerialized getSerialized, + OrthancPluginJobStep step, + OrthancPluginJobStop stop, + OrthancPluginJobReset reset) + { + OrthancPluginJob* target = NULL; + + _OrthancPluginCreateJob params; + memset(¶ms, 0, sizeof(params)); + + params.target = ⌖ + params.job = job; + params.finalize = finalize; + params.type = type; + params.getProgress = getProgress; + params.getContent = getContent; + params.getSerialized = getSerialized; + params.step = step; + params.stop = stop; + params.reset = reset; + + if (context->InvokeService(context, _OrthancPluginService_CreateJob, ¶ms) != OrthancPluginErrorCode_Success || + target == NULL) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginJob* job; + } _OrthancPluginFreeJob; + + /** + * @brief Free a custom job. + * + * This function frees an image that was created with OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeJob( + OrthancPluginContext* context, + OrthancPluginJob* job) + { + _OrthancPluginFreeJob params; + params.job = job; + + context->InvokeService(context, _OrthancPluginService_FreeJob, ¶ms); + } + + + + typedef struct + { + char** resultId; + OrthancPluginJob *job; + int priority; + } _OrthancPluginSubmitJob; + + /** + * @brief Submit a new job to the jobs engine of Orthanc. + * + * This function adds the given job to the pending jobs of + * Orthanc. Orthanc will take take of freeing it by invoking the + * finalization callback provided to OrthancPluginCreateJob(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param job The job, as received by OrthancPluginCreateJob(). + * @param priority The priority of the job. + * @return ID of the newly-submitted job. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginSubmitJob( + OrthancPluginContext *context, + OrthancPluginJob *job, + int priority) + { + char* resultId = NULL; + + _OrthancPluginSubmitJob params; + memset(¶ms, 0, sizeof(params)); + + params.resultId = &resultId; + params.job = job; + params.priority = priority; + + if (context->InvokeService(context, _OrthancPluginService_SubmitJob, ¶ms) != OrthancPluginErrorCode_Success || + resultId == NULL) + { + /* Error */ + return NULL; + } + else + { + return resultId; + } + } + + + + typedef struct + { + OrthancPluginJobsUnserializer unserializer; + } _OrthancPluginJobsUnserializer; + + /** + * @brief Register an unserializer for custom jobs. + * + * This function registers an unserializer that decodes custom jobs + * from a JSON string. This callback is invoked when the jobs engine + * of Orthanc is started (on Orthanc initialization), for each job + * that is stored in the Orthanc database. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param unserializer The job unserializer. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterJobsUnserializer( + OrthancPluginContext* context, + OrthancPluginJobsUnserializer unserializer) + { + _OrthancPluginJobsUnserializer params; + params.unserializer = unserializer; + + context->InvokeService(context, _OrthancPluginService_RegisterJobsUnserializer, ¶ms); + } + + #ifdef __cplusplus } #endif
--- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1895 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -#pragma once - -#include "OrthancCDatabasePlugin.h" - -#include <stdexcept> -#include <list> -#include <string> - -namespace OrthancPlugins -{ -//! @cond Doxygen_Suppress - // This class mimics "boost::noncopyable" - class NonCopyable - { - private: - NonCopyable(const NonCopyable&); - - NonCopyable& operator= (const NonCopyable&); - - protected: - NonCopyable() - { - } - - ~NonCopyable() - { - } - }; -//! @endcond - - - /** - * @ingroup Callbacks - **/ - class DatabaseException - { - private: - OrthancPluginErrorCode code_; - - public: - DatabaseException() : code_(OrthancPluginErrorCode_DatabasePlugin) - { - } - - DatabaseException(OrthancPluginErrorCode code) : code_(code) - { - } - - OrthancPluginErrorCode GetErrorCode() const - { - return code_; - } - }; - - - /** - * @ingroup Callbacks - **/ - class DatabaseBackendOutput : public NonCopyable - { - friend class DatabaseBackendAdapter; - - private: - enum AllowedAnswers - { - AllowedAnswers_All, - AllowedAnswers_None, - AllowedAnswers_Attachment, - AllowedAnswers_Change, - AllowedAnswers_DicomTag, - AllowedAnswers_ExportedResource - }; - - OrthancPluginContext* context_; - OrthancPluginDatabaseContext* database_; - AllowedAnswers allowedAnswers_; - - void SetAllowedAnswers(AllowedAnswers allowed) - { - allowedAnswers_ = allowed; - } - - public: - DatabaseBackendOutput(OrthancPluginContext* context, - OrthancPluginDatabaseContext* database) : - context_(context), - database_(database), - allowedAnswers_(AllowedAnswers_All /* for unit tests */) - { - } - - OrthancPluginContext* GetContext() - { - return context_; - } - - void LogError(const std::string& message) - { - OrthancPluginLogError(context_, message.c_str()); - } - - void LogWarning(const std::string& message) - { - OrthancPluginLogWarning(context_, message.c_str()); - } - - void LogInfo(const std::string& message) - { - OrthancPluginLogInfo(context_, message.c_str()); - } - - void SignalDeletedAttachment(const std::string& uuid, - int32_t contentType, - uint64_t uncompressedSize, - const std::string& uncompressedHash, - int32_t compressionType, - uint64_t compressedSize, - const std::string& compressedHash) - { - OrthancPluginAttachment attachment; - attachment.uuid = uuid.c_str(); - attachment.contentType = contentType; - attachment.uncompressedSize = uncompressedSize; - attachment.uncompressedHash = uncompressedHash.c_str(); - attachment.compressionType = compressionType; - attachment.compressedSize = compressedSize; - attachment.compressedHash = compressedHash.c_str(); - - OrthancPluginDatabaseSignalDeletedAttachment(context_, database_, &attachment); - } - - void SignalDeletedResource(const std::string& publicId, - OrthancPluginResourceType resourceType) - { - OrthancPluginDatabaseSignalDeletedResource(context_, database_, publicId.c_str(), resourceType); - } - - void SignalRemainingAncestor(const std::string& ancestorId, - OrthancPluginResourceType ancestorType) - { - OrthancPluginDatabaseSignalRemainingAncestor(context_, database_, ancestorId.c_str(), ancestorType); - } - - void AnswerAttachment(const std::string& uuid, - int32_t contentType, - uint64_t uncompressedSize, - const std::string& uncompressedHash, - int32_t compressionType, - uint64_t compressedSize, - const std::string& compressedHash) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_Attachment) - { - throw std::runtime_error("Cannot answer with an attachment in the current state"); - } - - OrthancPluginAttachment attachment; - attachment.uuid = uuid.c_str(); - attachment.contentType = contentType; - attachment.uncompressedSize = uncompressedSize; - attachment.uncompressedHash = uncompressedHash.c_str(); - attachment.compressionType = compressionType; - attachment.compressedSize = compressedSize; - attachment.compressedHash = compressedHash.c_str(); - - OrthancPluginDatabaseAnswerAttachment(context_, database_, &attachment); - } - - void AnswerChange(int64_t seq, - int32_t changeType, - OrthancPluginResourceType resourceType, - const std::string& publicId, - const std::string& date) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_Change) - { - throw std::runtime_error("Cannot answer with a change in the current state"); - } - - OrthancPluginChange change; - change.seq = seq; - change.changeType = changeType; - change.resourceType = resourceType; - change.publicId = publicId.c_str(); - change.date = date.c_str(); - - OrthancPluginDatabaseAnswerChange(context_, database_, &change); - } - - void AnswerDicomTag(uint16_t group, - uint16_t element, - const std::string& value) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_DicomTag) - { - throw std::runtime_error("Cannot answer with a DICOM tag in the current state"); - } - - OrthancPluginDicomTag tag; - tag.group = group; - tag.element = element; - tag.value = value.c_str(); - - OrthancPluginDatabaseAnswerDicomTag(context_, database_, &tag); - } - - void AnswerExportedResource(int64_t seq, - OrthancPluginResourceType resourceType, - const std::string& publicId, - const std::string& modality, - const std::string& date, - const std::string& patientId, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_ExportedResource) - { - throw std::runtime_error("Cannot answer with an exported resource in the current state"); - } - - OrthancPluginExportedResource exported; - exported.seq = seq; - exported.resourceType = resourceType; - exported.publicId = publicId.c_str(); - exported.modality = modality.c_str(); - exported.date = date.c_str(); - exported.patientId = patientId.c_str(); - exported.studyInstanceUid = studyInstanceUid.c_str(); - exported.seriesInstanceUid = seriesInstanceUid.c_str(); - exported.sopInstanceUid = sopInstanceUid.c_str(); - - OrthancPluginDatabaseAnswerExportedResource(context_, database_, &exported); - } - }; - - - /** - * @ingroup Callbacks - **/ - class IDatabaseBackend : public NonCopyable - { - friend class DatabaseBackendAdapter; - - private: - DatabaseBackendOutput* output_; - - void Finalize() - { - if (output_ != NULL) - { - delete output_; - output_ = NULL; - } - } - - protected: - DatabaseBackendOutput& GetOutput() - { - return *output_; - } - - public: - IDatabaseBackend() : output_(NULL) - { - } - - virtual ~IDatabaseBackend() - { - Finalize(); - } - - // This takes the ownership - void RegisterOutput(DatabaseBackendOutput* output) - { - Finalize(); - output_ = output; - } - - virtual void Open() = 0; - - virtual void Close() = 0; - - virtual void AddAttachment(int64_t id, - const OrthancPluginAttachment& attachment) = 0; - - virtual void AttachChild(int64_t parent, - int64_t child) = 0; - - virtual void ClearChanges() = 0; - - virtual void ClearExportedResources() = 0; - - virtual int64_t CreateResource(const char* publicId, - OrthancPluginResourceType type) = 0; - - virtual void DeleteAttachment(int64_t id, - int32_t attachment) = 0; - - virtual void DeleteMetadata(int64_t id, - int32_t metadataType) = 0; - - virtual void DeleteResource(int64_t id) = 0; - - virtual void GetAllInternalIds(std::list<int64_t>& target, - OrthancPluginResourceType resourceType) = 0; - - virtual void GetAllPublicIds(std::list<std::string>& target, - OrthancPluginResourceType resourceType) = 0; - - virtual void GetAllPublicIds(std::list<std::string>& target, - OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) = 0; - - /* Use GetOutput().AnswerChange() */ - virtual void GetChanges(bool& done /*out*/, - int64_t since, - uint32_t maxResults) = 0; - - virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/, - int64_t id) = 0; - - virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/, - int64_t id) = 0; - - /* Use GetOutput().AnswerExportedResource() */ - virtual void GetExportedResources(bool& done /*out*/, - int64_t since, - uint32_t maxResults) = 0; - - /* Use GetOutput().AnswerChange() */ - virtual void GetLastChange() = 0; - - /* Use GetOutput().AnswerExportedResource() */ - virtual void GetLastExportedResource() = 0; - - /* Use GetOutput().AnswerDicomTag() */ - virtual void GetMainDicomTags(int64_t id) = 0; - - virtual std::string GetPublicId(int64_t resourceId) = 0; - - virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) = 0; - - virtual OrthancPluginResourceType GetResourceType(int64_t resourceId) = 0; - - virtual uint64_t GetTotalCompressedSize() = 0; - - virtual uint64_t GetTotalUncompressedSize() = 0; - - virtual bool IsExistingResource(int64_t internalId) = 0; - - virtual bool IsProtectedPatient(int64_t internalId) = 0; - - virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/, - int64_t id) = 0; - - virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/, - int64_t id) = 0; - - virtual void LogChange(const OrthancPluginChange& change) = 0; - - virtual void LogExportedResource(const OrthancPluginExportedResource& resource) = 0; - - /* Use GetOutput().AnswerAttachment() */ - virtual bool LookupAttachment(int64_t id, - int32_t contentType) = 0; - - virtual bool LookupGlobalProperty(std::string& target /*out*/, - int32_t property) = 0; - - virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, - OrthancPluginResourceType resourceType, - uint16_t group, - uint16_t element, - OrthancPluginIdentifierConstraint constraint, - const char* value) = 0; - - virtual bool LookupMetadata(std::string& target /*out*/, - int64_t id, - int32_t metadataType) = 0; - - virtual bool LookupParent(int64_t& parentId /*out*/, - int64_t resourceId) = 0; - - virtual bool LookupResource(int64_t& id /*out*/, - OrthancPluginResourceType& type /*out*/, - const char* publicId) = 0; - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) = 0; - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/, - int64_t patientIdToAvoid) = 0; - - virtual void SetGlobalProperty(int32_t property, - const char* value) = 0; - - virtual void SetMainDicomTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) = 0; - - virtual void SetIdentifierTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) = 0; - - virtual void SetMetadata(int64_t id, - int32_t metadataType, - const char* value) = 0; - - virtual void SetProtectedPatient(int64_t internalId, - bool isProtected) = 0; - - virtual void StartTransaction() = 0; - - virtual void RollbackTransaction() = 0; - - virtual void CommitTransaction() = 0; - - virtual uint32_t GetDatabaseVersion() = 0; - - /** - * Upgrade the database to the specified version of the database - * schema. The upgrade script is allowed to make calls to - * OrthancPluginReconstructMainDicomTags(). - **/ - virtual void UpgradeDatabase(uint32_t targetVersion, - OrthancPluginStorageArea* storageArea) = 0; - - virtual void ClearMainDicomTags(int64_t internalId) = 0; - }; - - - - /** - * @brief Bridge between C and C++ database engines. - * - * Class creating the bridge between the C low-level primitives for - * custom database engines, and the high-level IDatabaseBackend C++ - * interface. - * - * @ingroup Callbacks - **/ - class DatabaseBackendAdapter - { - private: - // This class cannot be instantiated - DatabaseBackendAdapter() - { - } - - static void LogError(IDatabaseBackend* backend, - const std::runtime_error& e) - { - backend->GetOutput().LogError("Exception in database back-end: " + std::string(e.what())); - } - - - static OrthancPluginErrorCode AddAttachment(void* payload, - int64_t id, - const OrthancPluginAttachment* attachment) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->AddAttachment(id, *attachment); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode AttachChild(void* payload, - int64_t parent, - int64_t child) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->AttachChild(parent, child); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ClearChanges(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->ClearChanges(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ClearExportedResources(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->ClearExportedResources(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode CreateResource(int64_t* id, - void* payload, - const char* publicId, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *id = backend->CreateResource(publicId, resourceType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode DeleteAttachment(void* payload, - int64_t id, - int32_t contentType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteAttachment(id, contentType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode DeleteMetadata(void* payload, - int64_t id, - int32_t metadataType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteMetadata(id, metadataType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode DeleteResource(void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteResource(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetAllInternalIds(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->GetAllInternalIds(target, resourceType); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetAllPublicIds(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetAllPublicIds(ids, resourceType); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetAllPublicIdsWithLimit(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetAllPublicIds(ids, resourceType, since, limit); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetChanges(OrthancPluginDatabaseContext* context, - void* payload, - int64_t since, - uint32_t maxResult) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); - - try - { - bool done; - backend->GetChanges(done, since, maxResult); - - if (done) - { - OrthancPluginDatabaseAnswerChangesDone(backend->GetOutput().context_, - backend->GetOutput().database_); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetChildrenInternalId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->GetChildrenInternalId(target, id); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetChildrenPublicId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetChildrenPublicId(ids, id); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetExportedResources(OrthancPluginDatabaseContext* context, - void* payload, - int64_t since, - uint32_t maxResult) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); - - try - { - bool done; - backend->GetExportedResources(done, since, maxResult); - - if (done) - { - OrthancPluginDatabaseAnswerExportedResourcesDone(backend->GetOutput().context_, - backend->GetOutput().database_); - } - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetLastChange(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); - - try - { - backend->GetLastChange(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetLastExportedResource(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); - - try - { - backend->GetLastExportedResource(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetMainDicomTags(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_DicomTag); - - try - { - backend->GetMainDicomTags(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetPublicId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s = backend->GetPublicId(id); - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - s.c_str()); - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetResourceCount(uint64_t* target, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetResourceCount(resourceType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetResourceType(OrthancPluginResourceType* resourceType, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *resourceType = backend->GetResourceType(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetTotalCompressedSize(uint64_t* target, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetTotalCompressedSize(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetTotalUncompressedSize(uint64_t* target, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetTotalUncompressedSize(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode IsExistingResource(int32_t* existing, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *existing = backend->IsExistingResource(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode IsProtectedPatient(int32_t* isProtected, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *isProtected = backend->IsProtectedPatient(id); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ListAvailableMetadata(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int32_t> target; - backend->ListAvailableMetadata(target, id); - - for (std::list<int32_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, - backend->GetOutput().database_, - *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ListAvailableAttachments(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int32_t> target; - backend->ListAvailableAttachments(target, id); - - for (std::list<int32_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, - backend->GetOutput().database_, - *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LogChange(void* payload, - const OrthancPluginChange* change) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->LogChange(*change); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LogExportedResource(void* payload, - const OrthancPluginExportedResource* exported) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->LogExportedResource(*exported); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupAttachment(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id, - int32_t contentType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Attachment); - - try - { - backend->LookupAttachment(id, contentType); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupGlobalProperty(OrthancPluginDatabaseContext* context, - void* payload, - int32_t property) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s; - if (backend->LookupGlobalProperty(s, property)) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - s.c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupIdentifier3(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType, - const OrthancPluginDicomTag* tag, - OrthancPluginIdentifierConstraint constraint) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->LookupIdentifier(target, resourceType, tag->group, tag->element, constraint, tag->value); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupMetadata(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id, - int32_t metadata) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s; - if (backend->LookupMetadata(s, id, metadata)) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, s.c_str()); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupParent(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t parent; - if (backend->LookupParent(parent, id)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, parent); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode LookupResource(OrthancPluginDatabaseContext* context, - void* payload, - const char* publicId) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - OrthancPluginResourceType type; - if (backend->LookupResource(id, type, publicId)) - { - OrthancPluginDatabaseAnswerResource(backend->GetOutput().context_, - backend->GetOutput().database_, - id, type); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SelectPatientToRecycle(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - if (backend->SelectPatientToRecycle(id)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, id); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SelectPatientToRecycle2(OrthancPluginDatabaseContext* context, - void* payload, - int64_t patientIdToAvoid) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - if (backend->SelectPatientToRecycle(id, patientIdToAvoid)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, id); - } - - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetGlobalProperty(void* payload, - int32_t property, - const char* value) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetGlobalProperty(property, value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetMainDicomTag(void* payload, - int64_t id, - const OrthancPluginDicomTag* tag) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetMainDicomTag(id, tag->group, tag->element, tag->value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetIdentifierTag(void* payload, - int64_t id, - const OrthancPluginDicomTag* tag) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetIdentifierTag(id, tag->group, tag->element, tag->value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetMetadata(void* payload, - int64_t id, - int32_t metadata, - const char* value) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetMetadata(id, metadata, value); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode SetProtectedPatient(void* payload, - int64_t id, - int32_t isProtected) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetProtectedPatient(id, (isProtected != 0)); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode StartTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->StartTransaction(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode RollbackTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->RollbackTransaction(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode CommitTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->CommitTransaction(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode Open(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->Open(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode Close(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->Close(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode GetDatabaseVersion(uint32_t* version, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - - try - { - *version = backend->GetDatabaseVersion(); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode UpgradeDatabase(void* payload, - uint32_t targetVersion, - OrthancPluginStorageArea* storageArea) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - - try - { - backend->UpgradeDatabase(targetVersion, storageArea); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - static OrthancPluginErrorCode ClearMainDicomTags(void* payload, - int64_t internalId) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - - try - { - backend->ClearMainDicomTags(internalId); - return OrthancPluginErrorCode_Success; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return OrthancPluginErrorCode_DatabasePlugin; - } - catch (DatabaseException& e) - { - return e.GetErrorCode(); - } - } - - - public: - /** - * Register a custom database back-end written in C++. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param backend Your custom database engine. - **/ - - static void Register(OrthancPluginContext* context, - IDatabaseBackend& backend) - { - OrthancPluginDatabaseBackend params; - memset(¶ms, 0, sizeof(params)); - - OrthancPluginDatabaseExtensions extensions; - memset(&extensions, 0, sizeof(extensions)); - - params.addAttachment = AddAttachment; - params.attachChild = AttachChild; - params.clearChanges = ClearChanges; - params.clearExportedResources = ClearExportedResources; - params.createResource = CreateResource; - params.deleteAttachment = DeleteAttachment; - params.deleteMetadata = DeleteMetadata; - params.deleteResource = DeleteResource; - params.getAllPublicIds = GetAllPublicIds; - params.getChanges = GetChanges; - params.getChildrenInternalId = GetChildrenInternalId; - params.getChildrenPublicId = GetChildrenPublicId; - params.getExportedResources = GetExportedResources; - params.getLastChange = GetLastChange; - params.getLastExportedResource = GetLastExportedResource; - params.getMainDicomTags = GetMainDicomTags; - params.getPublicId = GetPublicId; - params.getResourceCount = GetResourceCount; - params.getResourceType = GetResourceType; - params.getTotalCompressedSize = GetTotalCompressedSize; - params.getTotalUncompressedSize = GetTotalUncompressedSize; - params.isExistingResource = IsExistingResource; - params.isProtectedPatient = IsProtectedPatient; - params.listAvailableMetadata = ListAvailableMetadata; - params.listAvailableAttachments = ListAvailableAttachments; - params.logChange = LogChange; - params.logExportedResource = LogExportedResource; - params.lookupAttachment = LookupAttachment; - params.lookupGlobalProperty = LookupGlobalProperty; - params.lookupIdentifier = NULL; // Unused starting with Orthanc 0.9.5 (db v6) - params.lookupIdentifier2 = NULL; // Unused starting with Orthanc 0.9.5 (db v6) - params.lookupMetadata = LookupMetadata; - params.lookupParent = LookupParent; - params.lookupResource = LookupResource; - params.selectPatientToRecycle = SelectPatientToRecycle; - params.selectPatientToRecycle2 = SelectPatientToRecycle2; - params.setGlobalProperty = SetGlobalProperty; - params.setMainDicomTag = SetMainDicomTag; - params.setIdentifierTag = SetIdentifierTag; - params.setMetadata = SetMetadata; - params.setProtectedPatient = SetProtectedPatient; - params.startTransaction = StartTransaction; - params.rollbackTransaction = RollbackTransaction; - params.commitTransaction = CommitTransaction; - params.open = Open; - params.close = Close; - - extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit; - extensions.getDatabaseVersion = GetDatabaseVersion; - extensions.upgradeDatabase = UpgradeDatabase; - extensions.clearMainDicomTags = ClearMainDicomTags; - extensions.getAllInternalIds = GetAllInternalIds; // New in Orthanc 0.9.5 (db v6) - extensions.lookupIdentifier3 = LookupIdentifier3; // New in Orthanc 0.9.5 (db v6) - - OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, ¶ms, &extensions, &backend); - if (!context) - { - throw std::runtime_error("Unable to register the database backend"); - } - - backend.RegisterOutput(new DatabaseBackendOutput(context, database)); - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/AutomatedJpeg2kCompression/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 2.8) + +project(Basic) + +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) + +add_library(AutomatedJpeg2kCompression SHARED Plugin.cpp)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,163 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 <orthanc/OrthancCPlugin.h> + +#include <string> + +static OrthancPluginContext* context_ = NULL; + + +static bool ReadFile(std::string& result, + const std::string& path) +{ + OrthancPluginMemoryBuffer tmp; + if (OrthancPluginReadFile(context_, &tmp, path.c_str()) == OrthancPluginErrorCode_Success) + { + result.assign(reinterpret_cast<const char*>(tmp.data), tmp.size); + OrthancPluginFreeMemoryBuffer(context_, &tmp); + return true; + } + else + { + return false; + } +} + + +OrthancPluginErrorCode OnStoredCallback(OrthancPluginDicomInstance* instance, + const char* instanceId) +{ + char buffer[1024]; + sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from origin %d (AET %s)", + (int) OrthancPluginGetInstanceSize(context_, instance), instanceId, + OrthancPluginGetInstanceOrigin(context_, instance), + OrthancPluginGetInstanceRemoteAet(context_, instance)); + OrthancPluginLogInfo(context_, buffer); + + if (OrthancPluginGetInstanceOrigin(context_, instance) == OrthancPluginInstanceOrigin_Plugin) + { + // Do not compress twice the same file + return OrthancPluginErrorCode_Success; + } + + // Write the uncompressed DICOM content to some temporary file + std::string uncompressed = "uncompressed-" + std::string(instanceId) + ".dcm"; + OrthancPluginErrorCode error = OrthancPluginWriteFile(context_, uncompressed.c_str(), + OrthancPluginGetInstanceData(context_, instance), + OrthancPluginGetInstanceSize(context_, instance)); + if (error) + { + return error; + } + + // Remove the original DICOM instance + std::string uri = "/instances/" + std::string(instanceId); + error = OrthancPluginRestApiDelete(context_, uri.c_str()); + if (error) + { + return error; + } + + // Path to the temporary file that will contain the compressed DICOM content + std::string compressed = "compressed-" + std::string(instanceId) + ".dcm"; + + // Compress to JPEG2000 using gdcm + std::string command1 = "gdcmconv --j2k " + uncompressed + " " + compressed; + + // Generate a new SOPInstanceUID for the JPEG2000 file, as gdcmconv + // does not do this by itself + std::string command2 = "dcmodify --no-backup -gin " + compressed; + + // Make the required system calls + system(command1.c_str()); + system(command2.c_str()); + + // Read the result of the JPEG2000 compression + std::string j2k; + bool ok = ReadFile(j2k, compressed); + + // Remove the two temporary files + remove(compressed.c_str()); + remove(uncompressed.c_str()); + + if (!ok) + { + return OrthancPluginErrorCode_Plugin; + } + + // Upload the JPEG2000 file through the REST API + OrthancPluginMemoryBuffer tmp; + if (OrthancPluginRestApiPost(context_, &tmp, "/instances", j2k.c_str(), j2k.size())) + { + ok = false; + } + + if (ok) + { + OrthancPluginFreeMemoryBuffer(context_, &tmp); + } + + return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_Plugin; +} + + +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[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context_->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + OrthancPluginRegisterOnStoredInstanceCallback(context_, OnStoredCallback); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "sample-jpeg2k"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return "0.0"; + } +}
--- a/Plugins/Samples/Basic/Plugin.c Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/Basic/Plugin.c Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -28,9 +29,9 @@ static OrthancPluginErrorCode customError; -ORTHANC_PLUGINS_API int32_t Callback1(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback1(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { char buffer[1024]; uint32_t i; @@ -57,7 +58,7 @@ OrthancPluginLogWarning(context, buffer); } - OrthancPluginLogWarning(context, ""); + OrthancPluginLogWarning(context, ""); for (i = 0; i < request->headersCount; i++) { @@ -67,13 +68,13 @@ OrthancPluginLogWarning(context, ""); - return 1; + return OrthancPluginErrorCode_Success; } -ORTHANC_PLUGINS_API int32_t Callback2(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback2(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { /* Answer with a sample 16bpp image. */ @@ -99,13 +100,13 @@ 256, 256, sizeof(uint16_t) * 256, buffer); } - return 0; + return OrthancPluginErrorCode_Success; } -ORTHANC_PLUGINS_API int32_t Callback3(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback3(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { if (request->method != OrthancPluginHttpMethod_Get) { @@ -124,13 +125,13 @@ } } - return 0; + return OrthancPluginErrorCode_Success; } -ORTHANC_PLUGINS_API int32_t Callback4(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback4(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { /* Answer with a sample 8bpp image. */ @@ -156,13 +157,13 @@ 256, 256, 256, buffer); } - return 0; + return OrthancPluginErrorCode_Success; } -ORTHANC_PLUGINS_API int32_t Callback5(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback5(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { /** * Demonstration the difference between the @@ -194,20 +195,18 @@ } else { - printf("ICI1\n"); error = OrthancPluginRestApiGetAfterPlugins(context, &tmp, request->groups[1]); - printf("ICI2\n"); } if (error) { - return -1; + return OrthancPluginErrorCode_InternalError; } else { OrthancPluginAnswerBuffer(context, output, tmp.data, tmp.size, "application/octet-stream"); OrthancPluginFreeMemoryBuffer(context, &tmp); - return 0; + return OrthancPluginErrorCode_Success; } } @@ -264,8 +263,9 @@ char* json; static int first = 1; - sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from AET %s", + sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from origin %d (AET %s)", (int) OrthancPluginGetInstanceSize(context, instance), instanceId, + OrthancPluginGetInstanceOrigin(context, instance), OrthancPluginGetInstanceRemoteAet(context, instance)); OrthancPluginLogWarning(context, buffer); @@ -323,11 +323,44 @@ } +ORTHANC_PLUGINS_API int32_t FilterIncomingHttpRequest(OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) +{ + uint32_t i; + + if (headersCount > 0) + { + OrthancPluginLogInfo(context, "HTTP headers of an incoming REST request:"); + for (i = 0; i < headersCount; i++) + { + char info[1024]; + sprintf(info, " %s: %s", headersKeys[i], headersValues[i]); + OrthancPluginLogInfo(context, info); + } + } + + if (method == OrthancPluginHttpMethod_Get || + method == OrthancPluginHttpMethod_Post) + { + return 1; /* Allowed */ + } + else + { + return 0; /* Only allow GET and POST requests */ + } +} + + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) { OrthancPluginMemoryBuffer tmp; char info[1024], *s; int counter, i; + OrthancPluginDictionaryEntry entry; context = c; OrthancPluginLogWarning(context, "Sample plugin is initializing"); @@ -384,8 +417,8 @@ OrthancPluginRegisterRestCallback(context, "/plugin/create", CallbackCreateDicom); OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback); - OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + OrthancPluginRegisterIncomingHttpRequestFilter(context, FilterIncomingHttpRequest); /* Declare several properties of the plugin */ OrthancPluginSetRootUri(context, "/plugin/hello"); @@ -408,6 +441,9 @@ OrthancPluginRegisterDictionaryTag(context, 0x0014, 0x1020, OrthancPluginValueRepresentation_DA, "ValidationExpiryDate", 1, 1); + OrthancPluginLookupDictionary(context, &entry, "ValidationExpiryDate"); + OrthancPluginLookupDictionary(context, &entry, "0010-0010"); + return 0; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/DicomDatasetReader.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,173 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomDatasetReader.h" + +#include "OrthancPluginException.h" + +#include <boost/lexical_cast.hpp> + +namespace OrthancPlugins +{ + // This function is copied-pasted from "../../../Core/Toolbox.cpp", + // in order to avoid the dependency of plugins against the Orthanc core + static std::string StripSpaces(const std::string& source) + { + size_t first = 0; + + while (first < source.length() && + isspace(source[first])) + { + first++; + } + + if (first == source.length()) + { + // String containing only spaces + return ""; + } + + size_t last = source.length(); + while (last > first && + isspace(source[last - 1])) + { + last--; + } + + assert(first <= last); + return source.substr(first, last - first); + } + + + DicomDatasetReader::DicomDatasetReader(const IDicomDataset& dataset) : + dataset_(dataset) + { + } + + + std::string DicomDatasetReader::GetStringValue(const DicomPath& path, + const std::string& defaultValue) const + { + std::string s; + if (dataset_.GetStringValue(s, path)) + { + return s; + } + else + { + return defaultValue; + } + } + + + std::string DicomDatasetReader::GetMandatoryStringValue(const DicomPath& path) const + { + std::string s; + if (dataset_.GetStringValue(s, path)) + { + return s; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InexistentTag); + } + } + + + template <typename T> + static bool GetValueInternal(T& target, + const IDicomDataset& dataset, + const DicomPath& path) + { + try + { + std::string s; + + if (dataset.GetStringValue(s, path)) + { + target = boost::lexical_cast<T>(StripSpaces(s)); + return true; + } + else + { + return false; + } + } + catch (boost::bad_lexical_cast&) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool DicomDatasetReader::GetIntegerValue(int& target, + const DicomPath& path) const + { + return GetValueInternal<int>(target, dataset_, path); + } + + + bool DicomDatasetReader::GetUnsignedIntegerValue(unsigned int& target, + const DicomPath& path) const + { + int value; + + if (!GetIntegerValue(value, path)) + { + return false; + } + else if (value >= 0) + { + target = static_cast<unsigned int>(value); + return true; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + bool DicomDatasetReader::GetFloatValue(float& target, + const DicomPath& path) const + { + return GetValueInternal<float>(target, dataset_, path); + } + + + bool DicomDatasetReader::GetDoubleValue(double& target, + const DicomPath& path) const + { + return GetValueInternal<double>(target, dataset_, path); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/DicomDatasetReader.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "IDicomDataset.h" + +#include <memory> +#include <vector> + +namespace OrthancPlugins +{ + class DicomDatasetReader : public boost::noncopyable + { + private: + const IDicomDataset& dataset_; + + public: + DicomDatasetReader(const IDicomDataset& dataset); + + const IDicomDataset& GetDataset() const + { + return dataset_; + } + + std::string GetStringValue(const DicomPath& path, + const std::string& defaultValue) const; + + std::string GetMandatoryStringValue(const DicomPath& path) const; + + bool GetIntegerValue(int& target, + const DicomPath& path) const; + + bool GetUnsignedIntegerValue(unsigned int& target, + const DicomPath& path) const; + + bool GetFloatValue(float& target, + const DicomPath& path) const; + + bool GetDoubleValue(double& target, + const DicomPath& path) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/DicomPath.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,87 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomPath.h" + +#include "OrthancPluginException.h" + +namespace OrthancPlugins +{ + const DicomPath::Prefix& DicomPath::GetPrefixItem(size_t depth) const + { + if (depth >= prefix_.size()) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + else + { + return prefix_[depth]; + } + } + + + DicomPath::DicomPath(const DicomTag& sequence, + size_t index, + const DicomTag& tag) : + finalTag_(tag) + { + AddToPrefix(sequence, index); + } + + + DicomPath::DicomPath(const DicomTag& sequence1, + size_t index1, + const DicomTag& sequence2, + size_t index2, + const DicomTag& tag) : + finalTag_(tag) + { + AddToPrefix(sequence1, index1); + AddToPrefix(sequence2, index2); + } + + + DicomPath::DicomPath(const DicomTag& sequence1, + size_t index1, + const DicomTag& sequence2, + size_t index2, + const DicomTag& sequence3, + size_t index3, + const DicomTag& tag) : + finalTag_(tag) + { + AddToPrefix(sequence1, index1); + AddToPrefix(sequence2, index2); + AddToPrefix(sequence3, index3); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/DicomPath.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,108 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomTag.h" + +#include <vector> +#include <stddef.h> + +namespace OrthancPlugins +{ + class DicomPath + { + private: + typedef std::pair<DicomTag, size_t> Prefix; + + std::vector<Prefix> prefix_; + DicomTag finalTag_; + + const Prefix& GetPrefixItem(size_t depth) const; + + public: + DicomPath(const DicomTag& finalTag) : + finalTag_(finalTag) + { + } + + DicomPath(const DicomTag& sequence, + size_t index, + const DicomTag& tag); + + DicomPath(const DicomTag& sequence1, + size_t index1, + const DicomTag& sequence2, + size_t index2, + const DicomTag& tag); + + DicomPath(const DicomTag& sequence1, + size_t index1, + const DicomTag& sequence2, + size_t index2, + const DicomTag& sequence3, + size_t index3, + const DicomTag& tag); + + void AddToPrefix(const DicomTag& tag, + size_t position) + { + prefix_.push_back(std::make_pair(tag, position)); + } + + size_t GetPrefixLength() const + { + return prefix_.size(); + } + + DicomTag GetPrefixTag(size_t depth) const + { + return GetPrefixItem(depth).first; + } + + size_t GetPrefixIndex(size_t depth) const + { + return GetPrefixItem(depth).second; + } + + const DicomTag& GetFinalTag() const + { + return finalTag_; + } + + void SetFinalTag(const DicomTag& tag) + { + finalTag_ = tag; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/DicomTag.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "DicomTag.h" + +#include "OrthancPluginException.h" + +namespace OrthancPlugins +{ + const char* DicomTag::GetName() const + { + if (*this == DICOM_TAG_BITS_STORED) + { + return "BitsStored"; + } + else if (*this == DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX) + { + return "ColumnPositionInTotalImagePixelMatrix"; + } + else if (*this == DICOM_TAG_COLUMNS) + { + return "Columns"; + } + else if (*this == DICOM_TAG_MODALITY) + { + return "Modality"; + } + else if (*this == DICOM_TAG_NUMBER_OF_FRAMES) + { + return "NumberOfFrames"; + } + else if (*this == DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE) + { + return "PerFrameFunctionalGroupsSequence"; + } + else if (*this == DICOM_TAG_PHOTOMETRIC_INTERPRETATION) + { + return "PhotometricInterpretation"; + } + else if (*this == DICOM_TAG_PIXEL_REPRESENTATION) + { + return "PixelRepresentation"; + } + else if (*this == DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE) + { + return "PlanePositionSlideSequence"; + } + else if (*this == DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX) + { + return "RowPositionInTotalImagePixelMatrix"; + } + else if (*this == DICOM_TAG_ROWS) + { + return "Rows"; + } + else if (*this == DICOM_TAG_SOP_CLASS_UID) + { + return "SOPClassUID"; + } + else if (*this == DICOM_TAG_SAMPLES_PER_PIXEL) + { + return "SamplesPerPixel"; + } + else if (*this == DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS) + { + return "TotalPixelMatrixColumns"; + } + else if (*this == DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS) + { + return "TotalPixelMatrixRows"; + } + else if (*this == DICOM_TAG_TRANSFER_SYNTAX_UID) + { + return "TransferSyntaxUID"; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotImplemented); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/DicomTag.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,106 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 <stdint.h> + +namespace OrthancPlugins +{ + class DicomTag + { + private: + uint16_t group_; + uint16_t element_; + + DicomTag(); // Forbidden + + public: + DicomTag(uint16_t group, + uint16_t element) : + group_(group), + element_(element) + { + } + + uint16_t GetGroup() const + { + return group_; + } + + uint16_t GetElement() const + { + return element_; + } + + const char* GetName() const; + + bool operator== (const DicomTag& other) const + { + return group_ == other.group_ && element_ == other.element_; + } + + bool operator!= (const DicomTag& other) const + { + return !(*this == other); + } + }; + + + static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101); + static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011); + static const DicomTag DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021e); + 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_MODALITY(0x0008, 0x0060); + static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); + static const DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE(0x5200, 0x9230); + static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); + static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); + static const DicomTag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030); + static const DicomTag DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE(0x0048, 0x021a); + static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052); + static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053); + static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010); + static const DicomTag DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021f); + static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002); + static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e); + static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050); + static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); + static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018); + static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS(0x0048, 0x0006); + static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS(0x0048, 0x0007); + static const DicomTag DICOM_TAG_TRANSFER_SYNTAX_UID(0x0002, 0x0010); + static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050); + static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/FullOrthancDataset.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,215 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "FullOrthancDataset.h" + +#include "OrthancPluginException.h" + +#include <stdio.h> +#include <cassert> + +namespace OrthancPlugins +{ + static const Json::Value* AccessTag(const Json::Value& dataset, + const DicomTag& tag) + { + if (dataset.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + char name[16]; + sprintf(name, "%04x,%04x", tag.GetGroup(), tag.GetElement()); + + if (!dataset.isMember(name)) + { + return NULL; + } + + const Json::Value& value = dataset[name]; + if (value.type() != Json::objectValue || + !value.isMember("Name") || + !value.isMember("Type") || + !value.isMember("Value") || + value["Name"].type() != Json::stringValue || + value["Type"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + return &value; + } + + + static const Json::Value& GetSequenceContent(const Json::Value& sequence) + { + assert(sequence.type() == Json::objectValue); + assert(sequence.isMember("Type")); + assert(sequence.isMember("Value")); + + const Json::Value& value = sequence["Value"]; + + if (sequence["Type"].asString() != "Sequence" || + value.type() != Json::arrayValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + return value; + } + } + + + static bool GetStringInternal(std::string& result, + const Json::Value& tag) + { + assert(tag.type() == Json::objectValue); + assert(tag.isMember("Type")); + assert(tag.isMember("Value")); + + const Json::Value& value = tag["Value"]; + + if (tag["Type"].asString() != "String" || + value.type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + result = value.asString(); + return true; + } + } + + + const Json::Value* FullOrthancDataset::LookupPath(const DicomPath& path) const + { + const Json::Value* content = &root_; + + for (unsigned int depth = 0; depth < path.GetPrefixLength(); depth++) + { + const Json::Value* sequence = AccessTag(*content, path.GetPrefixTag(depth)); + if (sequence == NULL) + { + return NULL; + } + + const Json::Value& nextContent = GetSequenceContent(*sequence); + + size_t index = path.GetPrefixIndex(depth); + if (index >= nextContent.size()) + { + return NULL; + } + else + { + content = &nextContent[static_cast<Json::Value::ArrayIndex>(index)]; + } + } + + return AccessTag(*content, path.GetFinalTag()); + } + + + void FullOrthancDataset::CheckRoot() const + { + if (root_.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + FullOrthancDataset::FullOrthancDataset(IOrthancConnection& orthanc, + const std::string& uri) + { + IOrthancConnection::RestApiGet(root_, orthanc, uri); + CheckRoot(); + } + + + FullOrthancDataset::FullOrthancDataset(const std::string& content) + { + IOrthancConnection::ParseJson(root_, content); + CheckRoot(); + } + + + FullOrthancDataset::FullOrthancDataset(const void* content, + size_t size) + { + IOrthancConnection::ParseJson(root_, content, size); + CheckRoot(); + } + + + FullOrthancDataset::FullOrthancDataset(const Json::Value& root) : + root_(root) + { + CheckRoot(); + } + + + bool FullOrthancDataset::GetStringValue(std::string& result, + const DicomPath& path) const + { + const Json::Value* value = LookupPath(path); + + if (value == NULL) + { + return false; + } + else + { + return GetStringInternal(result, *value); + } + } + + + bool FullOrthancDataset::GetSequenceSize(size_t& size, + const DicomPath& path) const + { + const Json::Value* sequence = LookupPath(path); + + if (sequence == NULL) + { + return false; + } + else + { + size = GetSequenceContent(*sequence).size(); + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/FullOrthancDataset.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h" +#include "IDicomDataset.h" + +#include <json/value.h> + +namespace OrthancPlugins +{ + class FullOrthancDataset : public IDicomDataset + { + private: + Json::Value root_; + + const Json::Value* LookupPath(const DicomPath& path) const; + + void CheckRoot() const; + + public: + FullOrthancDataset(IOrthancConnection& orthanc, + const std::string& uri); + + FullOrthancDataset(const std::string& content); + + FullOrthancDataset(const void* content, + size_t size); + + FullOrthancDataset(const Json::Value& root); + + virtual bool GetStringValue(std::string& result, + const DicomPath& path) const; + + virtual bool GetSequenceSize(size_t& size, + const DicomPath& path) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/IDicomDataset.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomPath.h" + +#include <boost/noncopyable.hpp> +#include <string> + +namespace OrthancPlugins +{ + class IDicomDataset : public boost::noncopyable + { + public: + virtual ~IDicomDataset() + { + } + + virtual bool GetStringValue(std::string& result, + const DicomPath& path) const = 0; + + virtual bool GetSequenceSize(size_t& size, + const DicomPath& path) const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/IOrthancConnection.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,98 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h" + +#include "OrthancPluginException.h" + +#include <json/reader.h> + +namespace OrthancPlugins +{ + void IOrthancConnection::ParseJson(Json::Value& result, + const std::string& content) + { + Json::Reader reader; + + if (!reader.parse(content, result)) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void IOrthancConnection::ParseJson(Json::Value& result, + const void* content, + size_t size) + { + Json::Reader reader; + + if (!reader.parse(reinterpret_cast<const char*>(content), + reinterpret_cast<const char*>(content) + size, result)) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void IOrthancConnection::RestApiGet(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri) + { + std::string content; + orthanc.RestApiGet(content, uri); + ParseJson(result, content); + } + + + void IOrthancConnection::RestApiPost(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body) + { + std::string content; + orthanc.RestApiPost(content, uri, body); + ParseJson(result, content); + } + + + void IOrthancConnection::RestApiPut(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body) + { + std::string content; + orthanc.RestApiPut(content, uri, body); + ParseJson(result, content); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/IOrthancConnection.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "DicomPath.h" + +#include <boost/noncopyable.hpp> +#include <string> +#include <json/value.h> + +namespace OrthancPlugins +{ + class IOrthancConnection : public boost::noncopyable + { + public: + virtual ~IOrthancConnection() + { + } + + virtual void RestApiGet(std::string& result, + const std::string& uri) = 0; + + virtual void RestApiPost(std::string& result, + const std::string& uri, + const std::string& body) = 0; + + virtual void RestApiPut(std::string& result, + const std::string& uri, + const std::string& body) = 0; + + virtual void RestApiDelete(const std::string& uri) = 0; + + static void ParseJson(Json::Value& result, + const std::string& content); + + static void ParseJson(Json::Value& result, + const void* content, + size_t size); + + static void RestApiGet(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri); + + static void RestApiPost(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body); + + static void RestApiPut(Json::Value& result, + IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancHttpConnection.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,108 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "OrthancHttpConnection.h" + +namespace OrthancPlugins +{ + void OrthancHttpConnection::Setup() + { + url_ = client_.GetUrl(); + + // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc) + client_.SetRedirectionFollowed(false); + } + + + OrthancHttpConnection::OrthancHttpConnection() : + client_(Orthanc::WebServiceParameters(), "") + { + Setup(); + } + + + OrthancHttpConnection::OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters) : + client_(parameters, "") + { + Setup(); + } + + + void OrthancHttpConnection::RestApiGet(std::string& result, + const std::string& uri) + { + boost::mutex::scoped_lock lock(mutex_); + + client_.SetMethod(Orthanc::HttpMethod_Get); + client_.SetUrl(url_ + uri); + client_.ApplyAndThrowException(result); + } + + + void OrthancHttpConnection::RestApiPost(std::string& result, + const std::string& uri, + const std::string& body) + { + boost::mutex::scoped_lock lock(mutex_); + + client_.SetMethod(Orthanc::HttpMethod_Post); + client_.SetUrl(url_ + uri); + client_.SetBody(body); + client_.ApplyAndThrowException(result); + } + + + void OrthancHttpConnection::RestApiPut(std::string& result, + const std::string& uri, + const std::string& body) + { + boost::mutex::scoped_lock lock(mutex_); + + client_.SetMethod(Orthanc::HttpMethod_Put); + client_.SetUrl(url_ + uri); + client_.SetBody(body); + client_.ApplyAndThrowException(result); + } + + + void OrthancHttpConnection::RestApiDelete(const std::string& uri) + { + boost::mutex::scoped_lock lock(mutex_); + + std::string result; + + client_.SetMethod(Orthanc::HttpMethod_Delete); + client_.SetUrl(url_ + uri); + client_.ApplyAndThrowException(result); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancHttpConnection.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,76 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h" + +#if HAS_ORTHANC_EXCEPTION != 1 +# error The macro HAS_ORTHANC_EXCEPTION must be set to 1 if using this header +#endif + +#include "../../../Core/HttpClient.h" + +#include <boost/thread/mutex.hpp> + +namespace OrthancPlugins +{ + // This class is thread-safe + class OrthancHttpConnection : public IOrthancConnection + { + private: + boost::mutex mutex_; + Orthanc::HttpClient client_; + std::string url_; + + void Setup(); + + public: + OrthancHttpConnection(); + + OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters); + + virtual void RestApiGet(std::string& result, + const std::string& uri); + + virtual void RestApiPost(std::string& result, + const std::string& uri, + const std::string& body); + + virtual void RestApiPut(std::string& result, + const std::string& uri, + const std::string& body); + + virtual void RestApiDelete(const std::string& uri); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancPluginConnection.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "OrthancPluginConnection.h" + +#include "OrthancPluginCppWrapper.h" + +namespace OrthancPlugins +{ + void OrthancPluginConnection::RestApiGet(std::string& result, + const std::string& uri) + { + OrthancPlugins::MemoryBuffer buffer(context_); + + if (buffer.RestApiGet(uri, false)) + { + buffer.ToString(result); + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } + + + void OrthancPluginConnection::RestApiPost(std::string& result, + const std::string& uri, + const std::string& body) + { + OrthancPlugins::MemoryBuffer buffer(context_); + + if (buffer.RestApiPost(uri, body.c_str(), body.size(), false)) + { + buffer.ToString(result); + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } + + + void OrthancPluginConnection::RestApiPut(std::string& result, + const std::string& uri, + const std::string& body) + { + OrthancPlugins::MemoryBuffer buffer(context_); + + if (buffer.RestApiPut(uri, body.c_str(), body.size(), false)) + { + buffer.ToString(result); + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } + + + void OrthancPluginConnection::RestApiDelete(const std::string& uri) + { + OrthancPlugins::MemoryBuffer buffer(context_); + + if (!::OrthancPlugins::RestApiDelete(context_, uri, false)) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancPluginConnection.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h" + +#include <orthanc/OrthancCPlugin.h> + +namespace OrthancPlugins +{ + // This class is thread-safe + class OrthancPluginConnection : public IOrthancConnection + { + private: + OrthancPluginContext* context_; + + public: + OrthancPluginConnection(OrthancPluginContext* context) : + context_(context) + { + } + + virtual void RestApiGet(std::string& result, + const std::string& uri); + + virtual void RestApiPost(std::string& result, + const std::string& uri, + const std::string& body); + + virtual void RestApiPut(std::string& result, + const std::string& uri, + const std::string& body); + + virtual void RestApiDelete(const std::string& uri); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1939 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "OrthancPluginCppWrapper.h" + +#include <json/reader.h> +#include <json/writer.h> + + +namespace OrthancPlugins +{ + void MemoryBuffer::Check(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + } + + if (code == OrthancPluginErrorCode_Success) + { + return true; + } + else if (code == OrthancPluginErrorCode_UnknownResource || + code == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + MemoryBuffer::MemoryBuffer(OrthancPluginContext* context) : + context_(context) + { + buffer_.data = NULL; + buffer_.size = 0; + } + + + void MemoryBuffer::Clear() + { + if (buffer_.data != NULL) + { + OrthancPluginFreeMemoryBuffer(context_, &buffer_); + buffer_.data = NULL; + buffer_.size = 0; + } + } + + + void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) + { + Clear(); + + buffer_.data = other.data; + buffer_.size = other.size; + + other.data = NULL; + other.size = 0; + } + + + OrthancPluginMemoryBuffer MemoryBuffer::Release() + { + OrthancPluginMemoryBuffer result = buffer_; + + buffer_.data = NULL; + buffer_.size = 0; + + return result; + } + + + void MemoryBuffer::ToString(std::string& target) const + { + if (buffer_.size == 0) + { + target.clear(); + } + else + { + target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size); + } + } + + + void MemoryBuffer::ToJson(Json::Value& target) const + { + if (buffer_.data == NULL || + buffer_.size == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + const char* tmp = reinterpret_cast<const char*>(buffer_.data); + + Json::Reader reader; + if (!reader.parse(tmp, tmp + buffer_.size, target)) + { + OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool MemoryBuffer::RestApiGet(const std::string& uri, + bool applyPlugins) + { + Clear(); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiGetAfterPlugins(context_, &buffer_, uri.c_str())); + } + else + { + return CheckHttp(OrthancPluginRestApiGet(context_, &buffer_, uri.c_str())); + } + } + + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPostAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPutAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPut(context_, &buffer_, uri.c_str(), body, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(uri, writer.write(body), applyPlugins); + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(uri, writer.write(body), applyPlugins); + } + + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(context_, &buffer_, s.c_str(), NULL, flags)); + } + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(context_, &buffer_, s.c_str(), pixelData.GetObject(), flags)); + } + + + void MemoryBuffer::ReadFile(const std::string& path) + { + Clear(); + Check(OrthancPluginReadFile(context_, &buffer_, path.c_str())); + } + + + void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query) + { + Clear(); + Check(OrthancPluginWorklistGetDicomQuery(context_, &buffer_, query)); + } + + + void OrthancString::Assign(char* str) + { + if (str == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + Clear(); + str_ = str; + } + } + + + void OrthancString::Clear() + { + if (str_ != NULL) + { + OrthancPluginFreeString(context_, str_); + str_ = NULL; + } + } + + + void OrthancString::ToString(std::string& target) const + { + if (str_ == NULL) + { + target.clear(); + } + else + { + target.assign(str_); + } + } + + + void OrthancString::ToJson(Json::Value& target) const + { + if (str_ == NULL) + { + OrthancPluginLogError(context_, "Cannot convert an empty memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Reader reader; + if (!reader.parse(str_, target)) + { + OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void MemoryBuffer::DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + OrthancString str(context_); + str.Assign(OrthancPluginDicomBufferToJson(context_, GetData(), GetSize(), format, flags, maxStringLength)); + str.ToJson(target); + } + + + bool MemoryBuffer::HttpGet(const std::string& url, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpGet(context_, &buffer_, url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPost(context_, &buffer_, url.c_str(), + body.c_str(), body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPut(context_, &buffer_, url.c_str(), + body.empty() ? NULL : body.c_str(), + body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + void MemoryBuffer::GetDicomInstance(const std::string& instanceId) + { + Clear(); + Check(OrthancPluginGetDicomForInstance(context_, &buffer_, instanceId.c_str())); + } + + + bool HttpDelete(OrthancPluginContext* context_, + const std::string& url, + const std::string& username, + const std::string& password) + { + OrthancPluginErrorCode error = OrthancPluginHttpDelete + (context_, url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str()); + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + OrthancConfiguration::OrthancConfiguration(OrthancPluginContext* context) : + context_(context) + { + OrthancString str(context); + str.Assign(OrthancPluginGetConfiguration(context)); + + if (str.GetContent() == NULL) + { + OrthancPluginLogError(context, "Cannot access the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + str.ToJson(configuration_); + + if (configuration_.type() != Json::objectValue) + { + OrthancPluginLogError(context, "Unable to read the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + OrthancPluginContext* OrthancConfiguration::GetContext() const + { + if (context_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return context_; + } + } + + + std::string OrthancConfiguration::GetPath(const std::string& key) const + { + if (path_.empty()) + { + return key; + } + else + { + return path_ + "." + key; + } + } + + + bool OrthancConfiguration::IsSection(const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + return (configuration_.isMember(key) && + configuration_[key].type() == Json::objectValue); + } + + + void OrthancConfiguration::GetSection(OrthancConfiguration& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.context_ = context_; + target.path_ = GetPath(key); + + if (!configuration_.isMember(key)) + { + target.configuration_ = Json::objectValue; + } + else + { + if (configuration_[key].type() != Json::objectValue) + { + if (context_ != NULL) + { + std::string s = "The configuration section \"" + target.path_ + "\" is not an associative array as expected"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target.configuration_ = configuration_[key]; + } + } + + + bool OrthancConfiguration::LookupStringValue(std::string& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::stringValue) + { + if (context_ != NULL) + { + std::string s = "The configuration option \"" + GetPath(key) + "\" is not a string as expected"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asString(); + return true; + } + + + bool OrthancConfiguration::LookupIntegerValue(int& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::intValue: + target = configuration_[key].asInt(); + return true; + + case Json::uintValue: + target = configuration_[key].asUInt(); + return true; + + default: + if (context_ != NULL) + { + std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const + { + int tmp; + if (!LookupIntegerValue(tmp, key)) + { + return false; + } + + if (tmp < 0) + { + if (context_ != NULL) + { + std::string s = "The configuration option \"" + GetPath(key) + "\" is not a positive integer as expected"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + target = static_cast<unsigned int>(tmp); + return true; + } + } + + + bool OrthancConfiguration::LookupBooleanValue(bool& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::booleanValue) + { + if (context_ != NULL) + { + std::string s = "The configuration option \"" + GetPath(key) + "\" is not a Boolean as expected"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asBool(); + return true; + } + + + bool OrthancConfiguration::LookupFloatValue(float& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::realValue: + target = configuration_[key].asFloat(); + return true; + + case Json::intValue: + target = static_cast<float>(configuration_[key].asInt()); + return true; + + case Json::uintValue: + target = static_cast<float>(configuration_[key].asUInt()); + return true; + + default: + if (context_ != NULL) + { + std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target, + const std::string& key, + bool allowSingleString) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::arrayValue: + { + bool ok = true; + + for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++) + { + if (configuration_[key][i].type() == Json::stringValue) + { + target.push_back(configuration_[key][i].asString()); + } + else + { + ok = false; + } + } + + if (ok) + { + return true; + } + + break; + } + + case Json::stringValue: + if (allowSingleString) + { + target.push_back(configuration_[key].asString()); + return true; + } + + break; + + default: + break; + } + + if (context_ != NULL) + { + std::string s = ("The configuration option \"" + GetPath(key) + + "\" is not a list of strings as expected"); + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + + bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target, + const std::string& key, + bool allowSingleString) const + { + std::list<std::string> lst; + + if (LookupListOfStrings(lst, key, allowSingleString)) + { + target.clear(); + + for (std::list<std::string>::const_iterator + it = lst.begin(); it != lst.end(); ++it) + { + target.insert(*it); + } + + return true; + } + else + { + return false; + } + } + + + std::string OrthancConfiguration::GetStringValue(const std::string& key, + const std::string& defaultValue) const + { + std::string tmp; + if (LookupStringValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + int OrthancConfiguration::GetIntegerValue(const std::string& key, + int defaultValue) const + { + int tmp; + if (LookupIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const + { + unsigned int tmp; + if (LookupUnsignedIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + bool OrthancConfiguration::GetBooleanValue(const std::string& key, + bool defaultValue) const + { + bool tmp; + if (LookupBooleanValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + float OrthancConfiguration::GetFloatValue(const std::string& key, + float defaultValue) const + { + float tmp; + if (LookupFloatValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + void OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return; + } + + if (configuration_[key].type() != Json::objectValue) + { + if (context_ != NULL) + { + std::string s = "The configuration option \"" + GetPath(key) + "\" is not a string as expected"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + Json::Value::Members members = configuration_[key].getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = configuration_[key][members[i]]; + + if (value.type() == Json::stringValue) + { + target[members[i]] = value.asString(); + } + else + { + if (context_ != NULL) + { + std::string s = "The configuration option \"" + GetPath(key) + "\" is not a dictionary mapping strings to strings"; + OrthancPluginLogError(context_, s.c_str()); + } + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + } + + + void OrthancImage::Clear() + { + if (image_ != NULL) + { + OrthancPluginFreeImage(context_, image_); + image_ = NULL; + } + } + + + void OrthancImage::CheckImageAvailable() + { + if (image_ == NULL) + { + OrthancPluginLogError(context_, "Trying to access a NULL image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancImage::OrthancImage(OrthancPluginContext* context) : + context_(context), + image_(NULL) + { + if (context == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancImage::OrthancImage(OrthancPluginContext* context, + OrthancPluginImage* image) : + context_(context), + image_(image) + { + if (context == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancImage::OrthancImage(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) : + context_(context) + { + if (context == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + else + { + image_ = OrthancPluginCreateImage(context, format, width, height); + } + } + + OrthancImage::OrthancImage(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) : + context_(context) + { + if (context == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + else + { + image_ = OrthancPluginCreateImageAccessor(context, format, width, height, pitch, buffer); + } + } + + void OrthancImage::UncompressPngImage(const void* data, + size_t size) + { + Clear(); + image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Png); + if (image_ == NULL) + { + OrthancPluginLogError(context_, "Cannot uncompress a PNG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::UncompressJpegImage(const void* data, + size_t size) + { + Clear(); + image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Jpeg); + if (image_ == NULL) + { + OrthancPluginLogError(context_, "Cannot uncompress a JPEG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::DecodeDicomImage(const void* data, + size_t size, + unsigned int frame) + { + Clear(); + image_ = OrthancPluginDecodeDicomImage(context_, data, size, frame); + if (image_ == NULL) + { + OrthancPluginLogError(context_, "Cannot uncompress a DICOM image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancPluginPixelFormat OrthancImage::GetPixelFormat() + { + CheckImageAvailable(); + return OrthancPluginGetImagePixelFormat(context_, image_); + } + + + unsigned int OrthancImage::GetWidth() + { + CheckImageAvailable(); + return OrthancPluginGetImageWidth(context_, image_); + } + + + unsigned int OrthancImage::GetHeight() + { + CheckImageAvailable(); + return OrthancPluginGetImageHeight(context_, image_); + } + + + unsigned int OrthancImage::GetPitch() + { + CheckImageAvailable(); + return OrthancPluginGetImagePitch(context_, image_); + } + + + const void* OrthancImage::GetBuffer() + { + CheckImageAvailable(); + return OrthancPluginGetImageBuffer(context_, image_); + } + + + void OrthancImage::CompressPngImage(MemoryBuffer& target) + { + CheckImageAvailable(); + + OrthancPluginMemoryBuffer tmp; + OrthancPluginCompressPngImage(context_, &tmp, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + + target.Assign(tmp); + } + + + void OrthancImage::CompressJpegImage(MemoryBuffer& target, + uint8_t quality) + { + CheckImageAvailable(); + + OrthancPluginMemoryBuffer tmp; + OrthancPluginCompressJpegImage(context_, &tmp, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + + target.Assign(tmp); + } + + + void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerPngImage(context_, output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + } + + + void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality) + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerJpegImage(context_, output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + } + + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + FindMatcher::FindMatcher(OrthancPluginContext* context, + const OrthancPluginWorklistQuery* worklist) : + context_(context), + matcher_(NULL), + worklist_(worklist) + { + if (worklist_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void FindMatcher::SetupDicom(OrthancPluginContext* context, + const void* query, + uint32_t size) + { + context_ = context; + worklist_ = NULL; + + matcher_ = OrthancPluginCreateFindMatcher(context_, query, size); + if (matcher_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + FindMatcher::~FindMatcher() + { + // The "worklist_" field + + if (matcher_ != NULL) + { + OrthancPluginFreeFindMatcher(context_, matcher_); + } + } + + + + bool FindMatcher::IsMatch(const void* dicom, + uint32_t size) const + { + int32_t result; + + if (matcher_ != NULL) + { + result = OrthancPluginFindMatcherIsMatch(context_, matcher_, dicom, size); + } + else if (worklist_ != NULL) + { + result = OrthancPluginWorklistIsMatch(context_, worklist_, dicom, size); + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + if (result == 0) + { + return false; + } + else if (result == 1) + { + return true; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + +#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */ + + + bool RestApiGet(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + bool applyPlugins) + { + MemoryBuffer answer(context); + if (!answer.RestApiGet(uri, applyPlugins)) + { + return false; + } + else + { + answer.ToJson(result); + return true; + } + } + + + bool RestApiPost(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer(context); + if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + answer.ToJson(result); + return true; + } + } + + + bool RestApiPost(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(result, context, uri, writer.write(body), applyPlugins); + } + + + bool RestApiPut(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer(context); + if (!answer.RestApiPut(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + answer.ToJson(result); + return true; + } + } + + + bool RestApiPut(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(result, context, uri, writer.write(body), applyPlugins); + } + + + bool RestApiDelete(OrthancPluginContext* context, + const std::string& uri, + bool applyPlugins) + { + OrthancPluginErrorCode error; + + if (applyPlugins) + { + error = OrthancPluginRestApiDeleteAfterPlugins(context, uri.c_str()); + } + else + { + error = OrthancPluginRestApiDelete(context, uri.c_str()); + } + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + void ReportMinimalOrthancVersion(OrthancPluginContext* context, + unsigned int major, + unsigned int minor, + unsigned int revision) + { + std::string s = ("Your version of the Orthanc core (" + + std::string(context->orthancVersion) + + ") is too old to run this plugin (version " + + boost::lexical_cast<std::string>(major) + "." + + boost::lexical_cast<std::string>(minor) + "." + + boost::lexical_cast<std::string>(revision) + + " is required)"); + + OrthancPluginLogError(context, s.c_str()); + } + + + bool CheckMinimalOrthancVersion(OrthancPluginContext* context, + unsigned int major, + unsigned int minor, + unsigned int revision) + { + if (context == NULL) + { + OrthancPluginLogError(context, "Bad Orthanc context in the plugin"); + return false; + } + + if (!strcmp(context->orthancVersion, "mainline")) + { + // Assume compatibility with the mainline + return true; + } + + // Parse the version of the Orthanc core + int aa, bb, cc; + if ( + #ifdef _MSC_VER + sscanf_s + #else + sscanf + #endif + (context->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 || + aa < 0 || + bb < 0 || + cc < 0) + { + return false; + } + + unsigned int a = static_cast<unsigned int>(aa); + unsigned int b = static_cast<unsigned int>(bb); + unsigned int c = static_cast<unsigned int>(cc); + + // Check the major version number + + if (a > major) + { + return true; + } + + if (a < major) + { + return false; + } + + + // Check the minor version number + assert(a == major); + + if (b > minor) + { + return true; + } + + if (b < minor) + { + return false; + } + + // Check the patch level version number + assert(a == major && b == minor); + + if (c >= revision) + { + return true; + } + else + { + return false; + } + } + + + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + size_t OrthancPeers::GetPeerIndex(const std::string& name) const + { + size_t index; + if (LookupName(index, name)) + { + return index; + } + else + { + std::string s = "Inexistent peer: " + name; + OrthancPluginLogError(context_, s.c_str()); + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } + + + OrthancPeers::OrthancPeers(OrthancPluginContext* context) : + context_(context), + peers_(NULL), + timeout_(0) + { + if (context_ == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + peers_ = OrthancPluginGetPeers(context_); + + if (peers_ == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + uint32_t count = OrthancPluginGetPeersCount(context_, peers_); + + for (uint32_t i = 0; i < count; i++) + { + const char* name = OrthancPluginGetPeerName(context_, peers_, i); + if (name == NULL) + { + OrthancPluginFreePeers(context_, peers_); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + index_[name] = i; + } + } + + + OrthancPeers::~OrthancPeers() + { + if (peers_ != NULL) + { + OrthancPluginFreePeers(context_, peers_); + } + } + + + bool OrthancPeers::LookupName(size_t& target, + const std::string& name) const + { + Index::const_iterator found = index_.find(name); + + if (found == index_.end()) + { + return false; + } + else + { + target = found->second; + return true; + } + } + + + std::string OrthancPeers::GetPeerName(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerName(context_, peers_, static_cast<uint32_t>(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUrl(context_, peers_, static_cast<uint32_t>(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(const std::string& name) const + { + return GetPeerUrl(GetPeerIndex(name)); + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUserProperty(context_, peers_, static_cast<uint32_t>(index), key.c_str()); + if (s == NULL) + { + return false; + } + else + { + value.assign(s); + return true; + } + } + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const + { + return LookupUserProperty(value, GetPeerIndex(peer), key); + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPluginMemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (context_, &answer, NULL, &status, peers_, + static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Assign(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoGet(target, index, uri)); + } + + + bool OrthancPeers::DoGet(Json::Value& target, + size_t index, + const std::string& uri) const + { + MemoryBuffer buffer(context_); + + if (DoGet(buffer, index, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const + { + MemoryBuffer buffer(context_); + + if (DoGet(buffer, name, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPost(target, index, uri, body)); + } + + + bool OrthancPeers::DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer(context_); + + if (DoPost(buffer, index, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer(context_); + + if (DoPost(buffer, name, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPluginMemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (context_, &answer, NULL, &status, peers_, + static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Assign(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPluginMemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (context_, &answer, NULL, &status, peers_, + static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + OrthancPluginFreeMemoryBuffer(context_, &answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPut(index, uri, body)); + } + + + bool OrthancPeers::DoDelete(size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPluginMemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (context_, &answer, NULL, &status, peers_, + static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + OrthancPluginFreeMemoryBuffer(context_, &answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoDelete(const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoDelete(index, uri)); + } +#endif + + + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + void OrthancJob::CallbackFinalize(void* job) + { + if (job != NULL) + { + delete reinterpret_cast<OrthancJob*>(job); + } + } + + + float OrthancJob::CallbackGetProgress(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->progress_; + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetContent(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->content_.c_str(); + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetSerialized(void* job) + { + assert(job != NULL); + + try + { + const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job); + + if (tmp.hasSerialized_) + { + return tmp.serialized_.c_str(); + } + else + { + return NULL; + } + } + catch (...) + { + return 0; + } + } + + + OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->Step(); + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) + { + return OrthancPluginJobStepStatus_Failure; + } + catch (...) + { + return OrthancPluginJobStepStatus_Failure; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackStop(void* job, + OrthancPluginJobStopReason reason) + { + assert(job != NULL); + + try + { + reinterpret_cast<OrthancJob*>(job)->Stop(reason); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) + { + assert(job != NULL); + + try + { + reinterpret_cast<OrthancJob*>(job)->Reset(); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void OrthancJob::ClearContent() + { + Json::Value empty = Json::objectValue; + UpdateContent(empty); + } + + + void OrthancJob::UpdateContent(const Json::Value& content) + { + if (content.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + content_ = writer.write(content); + } + } + + + void OrthancJob::ClearSerialized() + { + hasSerialized_ = false; + serialized_.clear(); + } + + + void OrthancJob::UpdateSerialized(const Json::Value& serialized) + { + if (serialized.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + serialized_ = writer.write(serialized); + hasSerialized_ = true; + } + } + + + void OrthancJob::UpdateProgress(float progress) + { + if (progress < 0 || + progress > 1) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + progress_ = progress; + } + + + OrthancJob::OrthancJob(const std::string& jobType) : + jobType_(jobType), + progress_(0) + { + ClearContent(); + ClearSerialized(); + } + + + OrthancPluginJob* OrthancJob::Create(OrthancPluginContext* context, + OrthancJob* job) + { + if (job == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + OrthancPluginJob* orthanc = OrthancPluginCreateJob( + context, job, CallbackFinalize, job->jobType_.c_str(), + CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, + CallbackStep, CallbackStop, CallbackReset); + + if (orthanc == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return orthanc; + } + } + + + std::string OrthancJob::Submit(OrthancPluginContext* context, + OrthancJob* job, + int priority) + { + OrthancPluginJob* orthanc = Create(context, job); + + char* id = OrthancPluginSubmitJob(context, orthanc, priority); + + if (id == NULL) + { + OrthancPluginLogError(context, "Plugin cannot submit job"); + OrthancPluginFreeJob(context, orthanc); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + std::string tmp(id); + tmp.assign(id); + OrthancPluginFreeString(context, id); + + return tmp; + } + } +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,726 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "OrthancPluginException.h" + +#include <orthanc/OrthancCPlugin.h> +#include <boost/noncopyable.hpp> +#include <boost/lexical_cast.hpp> +#include <json/value.h> +#include <list> +#include <set> +#include <map> + + + +#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 && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) +// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0 +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 1 +#else +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 0 +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2) +# define HAS_ORTHANC_PLUGIN_PEERS 1 +# define HAS_ORTHANC_PLUGIN_JOB 1 +#else +# define HAS_ORTHANC_PLUGIN_PEERS 0 +# define HAS_ORTHANC_PLUGIN_JOB 0 +#endif + + + +namespace OrthancPlugins +{ + typedef void (*RestCallback) (OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + class OrthancImage; + + + class MemoryBuffer : public boost::noncopyable + { + private: + OrthancPluginContext* context_; + OrthancPluginMemoryBuffer buffer_; + + void Check(OrthancPluginErrorCode code); + + bool CheckHttp(OrthancPluginErrorCode code); + + public: + MemoryBuffer(OrthancPluginContext* context); + + ~MemoryBuffer() + { + Clear(); + } + + OrthancPluginMemoryBuffer* operator*() + { + return &buffer_; + } + + // This transfers ownership from "other" to "this" + void Assign(OrthancPluginMemoryBuffer& other); + + OrthancPluginMemoryBuffer Release(); + + const char* GetData() const + { + if (buffer_.size > 0) + { + return reinterpret_cast<const char*>(buffer_.data); + } + else + { + return NULL; + } + } + + size_t GetSize() const + { + return buffer_.size; + } + + void Clear(); + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + + bool RestApiGet(const std::string& uri, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + bool RestApiPut(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + void CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags); + + void CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags); + + void ReadFile(const std::string& path); + + void GetDicomQuery(const OrthancPluginWorklistQuery* query); + + void DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength); + + bool HttpGet(const std::string& url, + const std::string& username, + const std::string& password); + + bool HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + bool HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + void GetDicomInstance(const std::string& instanceId); + }; + + + class OrthancString : public boost::noncopyable + { + private: + OrthancPluginContext* context_; + char* str_; + + void Clear(); + + public: + OrthancString(OrthancPluginContext* context) : + context_(context), + str_(NULL) + { + } + + ~OrthancString() + { + Clear(); + } + + // This transfers ownership, warning: The string must have been + // allocated by the Orthanc core + void Assign(char* str); + + const char* GetContent() const + { + return str_; + } + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + }; + + + class OrthancConfiguration : public boost::noncopyable + { + private: + OrthancPluginContext* context_; + Json::Value configuration_; // Necessarily a Json::objectValue + std::string path_; + + std::string GetPath(const std::string& key) const; + + public: + OrthancConfiguration() : context_(NULL) + { + } + + OrthancConfiguration(OrthancPluginContext* context); + + OrthancPluginContext* GetContext() const; + + const Json::Value& GetJson() const + { + return configuration_; + } + + bool IsSection(const std::string& key) const; + + void GetSection(OrthancConfiguration& target, + const std::string& key) const; + + bool LookupStringValue(std::string& target, + const std::string& key) const; + + bool LookupIntegerValue(int& target, + const std::string& key) const; + + bool LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const; + + bool LookupBooleanValue(bool& target, + const std::string& key) const; + + bool LookupFloatValue(float& target, + const std::string& key) const; + + bool LookupListOfStrings(std::list<std::string>& target, + const std::string& key, + bool allowSingleString) const; + + bool LookupSetOfStrings(std::set<std::string>& target, + const std::string& key, + bool allowSingleString) const; + + std::string GetStringValue(const std::string& key, + const std::string& defaultValue) const; + + int GetIntegerValue(const std::string& key, + int defaultValue) const; + + unsigned int GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const; + + bool GetBooleanValue(const std::string& key, + bool defaultValue) const; + + float GetFloatValue(const std::string& key, + float defaultValue) const; + + void GetDictionary(std::map<std::string, std::string>& target, + const std::string& key) const; + }; + + class OrthancImage : public boost::noncopyable + { + private: + OrthancPluginContext* context_; + OrthancPluginImage* image_; + + void Clear(); + + void CheckImageAvailable(); + + public: + OrthancImage(OrthancPluginContext* context); + + OrthancImage(OrthancPluginContext* context, + OrthancPluginImage* image); + + OrthancImage(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height); + + OrthancImage(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer + ); + + ~OrthancImage() + { + Clear(); + } + + void UncompressPngImage(const void* data, + size_t size); + + void UncompressJpegImage(const void* data, + size_t size); + + void DecodeDicomImage(const void* data, + size_t size, + unsigned int frame); + + OrthancPluginPixelFormat GetPixelFormat(); + + unsigned int GetWidth(); + + unsigned int GetHeight(); + + unsigned int GetPitch(); + + const void* GetBuffer(); + + const OrthancPluginImage* GetObject() const + { + return image_; + } + + void CompressPngImage(MemoryBuffer& target); + + void CompressJpegImage(MemoryBuffer& target, + uint8_t quality); + + void AnswerPngImage(OrthancPluginRestOutput* output); + + void AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality); + }; + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + class FindMatcher : public boost::noncopyable + { + private: + OrthancPluginContext* context_; + OrthancPluginFindMatcher* matcher_; + const OrthancPluginWorklistQuery* worklist_; + + void SetupDicom(OrthancPluginContext* context, + const void* query, + uint32_t size); + + public: + FindMatcher(OrthancPluginContext* context, + const OrthancPluginWorklistQuery* worklist); + + FindMatcher(OrthancPluginContext* context, + const void* query, + uint32_t size) + { + SetupDicom(context, query, size); + } + + FindMatcher(OrthancPluginContext* context, + const MemoryBuffer& dicom) + { + SetupDicom(context, dicom.GetData(), dicom.GetSize()); + } + + ~FindMatcher(); + + bool IsMatch(const void* dicom, + uint32_t size) const; + + bool IsMatch(const MemoryBuffer& dicom) const + { + return IsMatch(dicom.GetData(), dicom.GetSize()); + } + }; +#endif + + + bool RestApiGet(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPost(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(result, context, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + bool RestApiPut(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const char* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPut(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(result, context, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + bool RestApiDelete(OrthancPluginContext* context, + const std::string& uri, + bool applyPlugins); + + bool HttpDelete(OrthancPluginContext* context, + const std::string& url, + const std::string& username, + const std::string& password); + + inline void LogError(OrthancPluginContext* context, + const std::string& message) + { + if (context != NULL) + { + OrthancPluginLogError(context, message.c_str()); + } + } + + inline void LogWarning(OrthancPluginContext* context, + const std::string& message) + { + if (context != NULL) + { + OrthancPluginLogWarning(context, message.c_str()); + } + } + + inline void LogInfo(OrthancPluginContext* context, + const std::string& message) + { + if (context != NULL) + { + OrthancPluginLogInfo(context, message.c_str()); + } + } + + void ReportMinimalOrthancVersion(OrthancPluginContext* context, + unsigned int major, + unsigned int minor, + unsigned int revision); + + bool CheckMinimalOrthancVersion(OrthancPluginContext* context, + unsigned int major, + unsigned int minor, + unsigned int revision); + + + namespace Internals + { + template <RestCallback Callback> + OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + Callback(output, url, request); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + } + + + template <RestCallback Callback> + void RegisterRestCallback(OrthancPluginContext* context, + const std::string& uri, + bool isThreadSafe) + { + if (isThreadSafe) + { + OrthancPluginRegisterRestCallbackNoLock(context, uri.c_str(), Internals::Protect<Callback>); + } + else + { + OrthancPluginRegisterRestCallback(context, uri.c_str(), Internals::Protect<Callback>); + } + } + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + class OrthancPeers : public boost::noncopyable + { + private: + typedef std::map<std::string, uint32_t> Index; + + OrthancPluginContext *context_; + OrthancPluginPeers *peers_; + Index index_; + uint32_t timeout_; + + size_t GetPeerIndex(const std::string& name) const; + + public: + OrthancPeers(OrthancPluginContext* context); + + ~OrthancPeers(); + + uint32_t GetTimeout() const + { + return timeout_; + } + + void SetTimeout(uint32_t timeout) + { + timeout_ = timeout; + } + + bool LookupName(size_t& target, + const std::string& name) const; + + std::string GetPeerName(size_t index) const; + + std::string GetPeerUrl(size_t index) const; + + std::string GetPeerUrl(const std::string& name) const; + + size_t GetPeersCount() const + { + return index_.size(); + } + + bool LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const; + + bool LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const; + + bool DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const; + + bool DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + size_t index, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const; + + bool DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPut(size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoDelete(size_t index, + const std::string& uri) const; + + bool DoDelete(const std::string& name, + const std::string& uri) const; + }; +#endif + + + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + class OrthancJob : public boost::noncopyable + { + private: + std::string jobType_; + std::string content_; + bool hasSerialized_; + std::string serialized_; + float progress_; + + static void CallbackFinalize(void* job); + + static float CallbackGetProgress(void* job); + + static const char* CallbackGetContent(void* job); + + static const char* CallbackGetSerialized(void* job); + + static OrthancPluginJobStepStatus CallbackStep(void* job); + + static OrthancPluginErrorCode CallbackStop(void* job, + OrthancPluginJobStopReason reason); + + static OrthancPluginErrorCode CallbackReset(void* job); + + protected: + void ClearContent(); + + void UpdateContent(const Json::Value& content); + + void ClearSerialized(); + + void UpdateSerialized(const Json::Value& serialized); + + void UpdateProgress(float progress); + + public: + OrthancJob(const std::string& jobType); + + virtual ~OrthancJob() + { + } + + virtual OrthancPluginJobStepStatus Step() = 0; + + virtual void Stop(OrthancPluginJobStopReason reason) = 0; + + virtual void Reset() = 0; + + static OrthancPluginJob* Create(OrthancPluginContext* context, + OrthancJob* job /* takes ownership */); + + static std::string Submit(OrthancPluginContext* context, + OrthancJob* job /* takes ownership */, + int priority); + }; +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancPluginException.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(HAS_ORTHANC_EXCEPTION) +# error The macro HAS_ORTHANC_EXCEPTION must be defined +#endif + + +#if HAS_ORTHANC_EXCEPTION == 1 +# include "../../../Core/OrthancException.h" +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::Orthanc::ErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::Orthanc::OrthancException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::Orthanc::ErrorCode_ ## code +#else +# include <orthanc/OrthancCPlugin.h> +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::OrthancPluginErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::OrthancPlugins::PluginException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::OrthancPluginErrorCode_ ## code +#endif + + +#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code)); + + +#define ORTHANC_PLUGINS_THROW_EXCEPTION(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code)); + + +#define ORTHANC_PLUGINS_CHECK_ERROR(code) \ + if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success)) \ + { \ + ORTHANC_PLUGINS_THROW_EXCEPTION(code); \ + } + + +namespace OrthancPlugins +{ +#if HAS_ORTHANC_EXCEPTION == 0 + class PluginException + { + private: + OrthancPluginErrorCode code_; + + public: + explicit PluginException(OrthancPluginErrorCode code) : code_(code) + { + } + + OrthancPluginErrorCode GetErrorCode() const + { + return code_; + } + + const char* What(OrthancPluginContext* context) const + { + const char* description = OrthancPluginGetErrorDescription(context, code_); + if (description) + { + return description; + } + else + { + return "No description available"; + } + } + }; +#endif +}
--- a/Plugins/Samples/Common/OrthancPlugins.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/Common/OrthancPlugins.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -17,7 +17,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") # Linking with "pthread" is necessary, otherwise the software crashes # http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17 - link_libraries(dl rt) + link_libraries(dl rt pthread) endif() @@ -27,3 +27,6 @@ if (MSVC) include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/) endif() + + +add_definitions(-DHAS_ORTHANC_EXCEPTION=0)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,157 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "SimplifiedOrthancDataset.h" + +#include "OrthancPluginException.h" + +namespace OrthancPlugins +{ + const Json::Value* SimplifiedOrthancDataset::LookupPath(const DicomPath& path) const + { + const Json::Value* content = &root_; + + for (unsigned int depth = 0; depth < path.GetPrefixLength(); depth++) + { + const char* name = path.GetPrefixTag(depth).GetName(); + if (content->type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + if (!content->isMember(name)) + { + return NULL; + } + + const Json::Value& sequence = (*content) [name]; + if (sequence.type() != Json::arrayValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + size_t index = path.GetPrefixIndex(depth); + if (index >= sequence.size()) + { + return NULL; + } + else + { + content = &sequence[static_cast<Json::Value::ArrayIndex>(index)]; + } + } + + const char* name = path.GetFinalTag().GetName(); + + if (content->type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + if (!content->isMember(name)) + { + return NULL; + } + else + { + return &((*content) [name]); + } + } + + + void SimplifiedOrthancDataset::CheckRoot() const + { + if (root_.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + SimplifiedOrthancDataset::SimplifiedOrthancDataset(IOrthancConnection& orthanc, + const std::string& uri) + { + IOrthancConnection::RestApiGet(root_, orthanc, uri); + CheckRoot(); + } + + + SimplifiedOrthancDataset::SimplifiedOrthancDataset(const std::string& content) + { + IOrthancConnection::ParseJson(root_, content); + CheckRoot(); + } + + + bool SimplifiedOrthancDataset::GetStringValue(std::string& result, + const DicomPath& path) const + { + const Json::Value* value = LookupPath(path); + + if (value == NULL) + { + return false; + } + else if (value->type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + result = value->asString(); + return true; + } + } + + + bool SimplifiedOrthancDataset::GetSequenceSize(size_t& size, + const DicomPath& path) const + { + const Json::Value* sequence = LookupPath(path); + + if (sequence == NULL) + { + // Inexistent path + return false; + } + else if (sequence->type() != Json::arrayValue) + { + // Not a sequence + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + size = sequence->size(); + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/SimplifiedOrthancDataset.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,62 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h" +#include "IDicomDataset.h" + +namespace OrthancPlugins +{ + class SimplifiedOrthancDataset : public IDicomDataset + { + private: + Json::Value root_; + + const Json::Value* LookupPath(const DicomPath& path) const; + + void CheckRoot() const; + + public: + SimplifiedOrthancDataset(IOrthancConnection& orthanc, + const std::string& uri); + + SimplifiedOrthancDataset(const std::string& content); + + virtual bool GetStringValue(std::string& result, + const DicomPath& path) const; + + virtual bool GetSequenceSize(size_t& size, + const DicomPath& path) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/CustomImageDecoder/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 2.8) + +project(CustomImageDecoder) + +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) + +add_library(PluginTest SHARED Plugin.cpp)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/CustomImageDecoder/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 <orthanc/OrthancCPlugin.h> + +static OrthancPluginContext* context_ = NULL; + + +static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex) +{ + *target = OrthancPluginCreateImage(context_, OrthancPluginPixelFormat_RGB24, 512, 512); + + memset(OrthancPluginGetImageBuffer(context_, *target), 128, + OrthancPluginGetImageHeight(context_, *target) * OrthancPluginGetImagePitch(context_, *target)); + + return OrthancPluginDrawText(context_, *target, 0, "Hello world", 100, 50, 255, 0, 0); +} + + +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[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context_->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + OrthancPluginRegisterDecodeImageCallback(context_, DecodeImageCallback); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "custom-image-decoder"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return "0.0"; + } +}
--- a/Plugins/Samples/DatabasePlugin/CMakeLists.txt Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -project(SampleDatabasePlugin) - -# Parameters of the build -SET(SAMPLE_DATABASE_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(STANDALONE_BUILD ON) - -# Advanced parameters to fine-tune linking against system libraries -SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") -SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") -SET(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite") - -set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) -include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) - -include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/SQLiteConfiguration.cmake) - -EmbedResources( - --system-exception # Use "std::runtime_error" instead of "OrthancException" for embedded resources - PREPARE_DATABASE ${ORTHANC_ROOT}/OrthancServer/PrepareDatabase.sql - ) - -message("Setting the version of the plugin to ${SAMPLE_DATABASE_VERSION}") - -add_definitions( - -DORTHANC_SQLITE_STANDALONE=1 - -DORTHANC_ENABLE_LOGGING=0 - -DORTHANC_ENABLE_BASE64=0 - -DORTHANC_ENABLE_MD5=0 - -DORTHANC_ENABLE_DCMTK=0 - -DORTHANC_PLUGINS_ENABLED=1 - -DSAMPLE_DATABASE_VERSION="${SAMPLE_DATABASE_VERSION}" - ) - -add_library(SampleDatabase SHARED - ${BOOST_SOURCES} - ${JSONCPP_SOURCES} - ${SQLITE_SOURCES} - ${AUTOGENERATED_SOURCES} - - ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp - ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp - ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp - ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp - ${ORTHANC_ROOT}/Core/Enumerations.cpp - ${ORTHANC_ROOT}/Core/SQLite/Connection.cpp - ${ORTHANC_ROOT}/Core/SQLite/FunctionContext.cpp - ${ORTHANC_ROOT}/Core/SQLite/Statement.cpp - ${ORTHANC_ROOT}/Core/SQLite/StatementId.cpp - ${ORTHANC_ROOT}/Core/SQLite/StatementReference.cpp - ${ORTHANC_ROOT}/Core/SQLite/Transaction.cpp - ${ORTHANC_ROOT}/Core/Toolbox.cpp - ${ORTHANC_ROOT}/OrthancServer/DatabaseWrapperBase.cpp - ${ORTHANC_ROOT}/Plugins/Engine/PluginsEnumerations.cpp - - Database.cpp - Plugin.cpp - ) - -set_target_properties(SampleDatabase PROPERTIES - VERSION ${SAMPLE_DATABASE_VERSION} - SOVERSION ${SAMPLE_DATABASE_VERSION}) - -install( - TARGETS SampleDatabase - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux - )
--- a/Plugins/Samples/DatabasePlugin/Database.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,562 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "Database.h" - -#include "../../../Core/DicomFormat/DicomArray.h" - -#include <EmbeddedResources.h> -#include <boost/lexical_cast.hpp> - - -namespace Internals -{ - class SignalFileDeleted : public Orthanc::SQLite::IScalarFunction - { - private: - OrthancPlugins::DatabaseBackendOutput& output_; - - public: - SignalFileDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output) - { - } - - virtual const char* GetName() const - { - return "SignalFileDeleted"; - } - - virtual unsigned int GetCardinality() const - { - return 7; - } - - virtual void Compute(Orthanc::SQLite::FunctionContext& context) - { - std::string uncompressedMD5, compressedMD5; - - if (!context.IsNullValue(5)) - { - uncompressedMD5 = context.GetStringValue(5); - } - - if (!context.IsNullValue(6)) - { - compressedMD5 = context.GetStringValue(6); - } - - output_.SignalDeletedAttachment(context.GetStringValue(0), - context.GetIntValue(1), - context.GetInt64Value(2), - uncompressedMD5, - context.GetIntValue(3), - context.GetInt64Value(4), - compressedMD5); - } - }; - - - class SignalResourceDeleted : public Orthanc::SQLite::IScalarFunction - { - private: - OrthancPlugins::DatabaseBackendOutput& output_; - - public: - SignalResourceDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output) - { - } - - virtual const char* GetName() const - { - return "SignalResourceDeleted"; - } - - virtual unsigned int GetCardinality() const - { - return 2; - } - - virtual void Compute(Orthanc::SQLite::FunctionContext& context) - { - output_.SignalDeletedResource(context.GetStringValue(0), - Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1)))); - } - }; -} - - -class Database::SignalRemainingAncestor : public Orthanc::SQLite::IScalarFunction -{ -private: - bool hasRemainingAncestor_; - std::string remainingPublicId_; - OrthancPluginResourceType remainingType_; - -public: - SignalRemainingAncestor() : - hasRemainingAncestor_(false) - { - } - - void Reset() - { - hasRemainingAncestor_ = false; - } - - virtual const char* GetName() const - { - return "SignalRemainingAncestor"; - } - - virtual unsigned int GetCardinality() const - { - return 2; - } - - virtual void Compute(Orthanc::SQLite::FunctionContext& context) - { - if (!hasRemainingAncestor_ || - remainingType_ >= context.GetIntValue(1)) - { - hasRemainingAncestor_ = true; - remainingPublicId_ = context.GetStringValue(0); - remainingType_ = Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1))); - } - } - - bool HasRemainingAncestor() const - { - return hasRemainingAncestor_; - } - - const std::string& GetRemainingAncestorId() const - { - assert(hasRemainingAncestor_); - return remainingPublicId_; - } - - OrthancPluginResourceType GetRemainingAncestorType() const - { - assert(hasRemainingAncestor_); - return remainingType_; - } -}; - - - -Database::Database(const std::string& path) : - path_(path), - base_(db_) -{ -} - - -void Database::Open() -{ - db_.Open(path_); - - db_.Execute("PRAGMA ENCODING=\"UTF-8\";"); - - // http://www.sqlite.org/pragma.html - db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;"); - db_.Execute("PRAGMA JOURNAL_MODE=WAL;"); - db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;"); - db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;"); - //db_.Execute("PRAGMA TEMP_STORE=memory"); - - if (!db_.DoesTableExist("GlobalProperties")) - { - std::string query; - Orthanc::EmbeddedResources::GetFileResource(query, Orthanc::EmbeddedResources::PREPARE_DATABASE); - db_.Execute(query); - } - - signalRemainingAncestor_ = new SignalRemainingAncestor; - db_.Register(signalRemainingAncestor_); - db_.Register(new Internals::SignalFileDeleted(GetOutput())); - db_.Register(new Internals::SignalResourceDeleted(GetOutput())); -} - - -void Database::Close() -{ - db_.Close(); -} - - -void Database::AddAttachment(int64_t id, - const OrthancPluginAttachment& attachment) -{ - Orthanc::FileInfo info(attachment.uuid, - static_cast<Orthanc::FileContentType>(attachment.contentType), - attachment.uncompressedSize, - attachment.uncompressedHash, - static_cast<Orthanc::CompressionType>(attachment.compressionType), - attachment.compressedSize, - attachment.compressedHash); - base_.AddAttachment(id, info); -} - - -void Database::DeleteResource(int64_t id) -{ - signalRemainingAncestor_->Reset(); - - Orthanc::SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); - s.BindInt64(0, id); - s.Run(); - - if (signalRemainingAncestor_->HasRemainingAncestor()) - { - GetOutput().SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorId(), - signalRemainingAncestor_->GetRemainingAncestorType()); - } -} - - -static void Answer(OrthancPlugins::DatabaseBackendOutput& output, - const Orthanc::ServerIndexChange& change) -{ - output.AnswerChange(change.GetSeq(), - change.GetChangeType(), - Orthanc::Plugins::Convert(change.GetResourceType()), - change.GetPublicId(), - change.GetDate()); -} - - -static void Answer(OrthancPlugins::DatabaseBackendOutput& output, - const Orthanc::ExportedResource& resource) -{ - output.AnswerExportedResource(resource.GetSeq(), - Orthanc::Plugins::Convert(resource.GetResourceType()), - resource.GetPublicId(), - resource.GetModality(), - resource.GetDate(), - resource.GetPatientId(), - resource.GetStudyInstanceUid(), - resource.GetSeriesInstanceUid(), - resource.GetSopInstanceUid()); -} - - -void Database::GetChanges(bool& done /*out*/, - int64_t since, - uint32_t maxResults) -{ - typedef std::list<Orthanc::ServerIndexChange> Changes; - - Changes changes; - base_.GetChanges(changes, done, since, maxResults); - - for (Changes::const_iterator it = changes.begin(); it != changes.end(); ++it) - { - Answer(GetOutput(), *it); - } -} - - -void Database::GetExportedResources(bool& done /*out*/, - int64_t since, - uint32_t maxResults) -{ - typedef std::list<Orthanc::ExportedResource> Resources; - - Resources resources; - base_.GetExportedResources(resources, done, since, maxResults); - - for (Resources::const_iterator it = resources.begin(); it != resources.end(); ++it) - { - Answer(GetOutput(), *it); - } -} - - -void Database::GetLastChange() -{ - std::list<Orthanc::ServerIndexChange> change; - Orthanc::ErrorCode code = base_.GetLastChange(change); - - if (code != Orthanc::ErrorCode_Success) - { - throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code)); - } - - if (!change.empty()) - { - Answer(GetOutput(), change.front()); - } -} - - -void Database::GetLastExportedResource() -{ - std::list<Orthanc::ExportedResource> resource; - base_.GetLastExportedResource(resource); - - if (!resource.empty()) - { - Answer(GetOutput(), resource.front()); - } -} - - -void Database::GetMainDicomTags(int64_t id) -{ - Orthanc::DicomMap tags; - base_.GetMainDicomTags(tags, id); - - Orthanc::DicomArray arr(tags); - for (size_t i = 0; i < arr.GetSize(); i++) - { - GetOutput().AnswerDicomTag(arr.GetElement(i).GetTag().GetGroup(), - arr.GetElement(i).GetTag().GetElement(), - arr.GetElement(i).GetValue().GetContent()); - } -} - - -std::string Database::GetPublicId(int64_t resourceId) -{ - std::string id; - if (base_.GetPublicId(id, resourceId)) - { - return id; - } - else - { - throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_UnknownResource); - } -} - - -OrthancPluginResourceType Database::GetResourceType(int64_t resourceId) -{ - Orthanc::ResourceType result; - Orthanc::ErrorCode code = base_.GetResourceType(result, resourceId); - - if (code == Orthanc::ErrorCode_Success) - { - return Orthanc::Plugins::Convert(result); - } - else - { - throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code)); - } -} - - - -template <typename I> -static void ConvertList(std::list<int32_t>& target, - const std::list<I>& source) -{ - for (typename std::list<I>::const_iterator - it = source.begin(); it != source.end(); it++) - { - target.push_back(*it); - } -} - - -void Database::ListAvailableMetadata(std::list<int32_t>& target /*out*/, - int64_t id) -{ - std::list<Orthanc::MetadataType> tmp; - base_.ListAvailableMetadata(tmp, id); - ConvertList(target, tmp); -} - - -void Database::ListAvailableAttachments(std::list<int32_t>& target /*out*/, - int64_t id) -{ - std::list<Orthanc::FileContentType> tmp; - base_.ListAvailableAttachments(tmp, id); - ConvertList(target, tmp); -} - - -void Database::LogChange(const OrthancPluginChange& change) -{ - int64_t id; - OrthancPluginResourceType type; - if (!LookupResource(id, type, change.publicId) || - type != change.resourceType) - { - throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_DatabasePlugin); - } - - Orthanc::ServerIndexChange tmp(change.seq, - static_cast<Orthanc::ChangeType>(change.changeType), - Orthanc::Plugins::Convert(change.resourceType), - change.publicId, - change.date); - - base_.LogChange(id, tmp); -} - - -void Database::LogExportedResource(const OrthancPluginExportedResource& resource) -{ - Orthanc::ExportedResource tmp(resource.seq, - Orthanc::Plugins::Convert(resource.resourceType), - resource.publicId, - resource.modality, - resource.date, - resource.patientId, - resource.studyInstanceUid, - resource.seriesInstanceUid, - resource.sopInstanceUid); - - base_.LogExportedResource(tmp); -} - - -bool Database::LookupAttachment(int64_t id, - int32_t contentType) -{ - Orthanc::FileInfo attachment; - if (base_.LookupAttachment(attachment, id, static_cast<Orthanc::FileContentType>(contentType))) - { - GetOutput().AnswerAttachment(attachment.GetUuid(), - attachment.GetContentType(), - attachment.GetUncompressedSize(), - attachment.GetUncompressedMD5(), - attachment.GetCompressionType(), - attachment.GetCompressedSize(), - attachment.GetCompressedMD5()); - return true; - } - else - { - return false; - } -} - - -bool Database::LookupParent(int64_t& parentId /*out*/, - int64_t resourceId) -{ - bool found; - Orthanc::ErrorCode code = base_.LookupParent(found, parentId, resourceId); - - if (code == Orthanc::ErrorCode_Success) - { - return found; - } - else - { - throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code)); - } -} - - -bool Database::LookupResource(int64_t& id /*out*/, - OrthancPluginResourceType& type /*out*/, - const char* publicId) -{ - Orthanc::ResourceType tmp; - if (base_.LookupResource(id, tmp, publicId)) - { - type = Orthanc::Plugins::Convert(tmp); - return true; - } - else - { - return false; - } -} - - -void Database::StartTransaction() -{ - transaction_.reset(new Orthanc::SQLite::Transaction(db_)); - transaction_->Begin(); -} - - -void Database::RollbackTransaction() -{ - transaction_->Rollback(); - transaction_.reset(NULL); -} - - -void Database::CommitTransaction() -{ - transaction_->Commit(); - transaction_.reset(NULL); -} - - -uint32_t Database::GetDatabaseVersion() -{ - std::string version; - - if (!LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion)) - { - throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError); - } - - try - { - return boost::lexical_cast<uint32_t>(version); - } - catch (boost::bad_lexical_cast&) - { - throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError); - } -} - - -void Database::UpgradeDatabase(uint32_t targetVersion, - OrthancPluginStorageArea* storageArea) -{ - if (targetVersion == 6) - { - OrthancPluginErrorCode code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, - OrthancPluginResourceType_Study); - if (code == OrthancPluginErrorCode_Success) - { - code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, - OrthancPluginResourceType_Series); - } - - if (code != OrthancPluginErrorCode_Success) - { - throw OrthancPlugins::DatabaseException(code); - } - - base_.SetGlobalProperty(Orthanc::GlobalProperty_DatabaseSchemaVersion, "6"); - } -}
--- a/Plugins/Samples/DatabasePlugin/Database.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,284 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <orthanc/OrthancCppDatabasePlugin.h> - -#include "../../../Core/SQLite/Connection.h" -#include "../../../Core/SQLite/Transaction.h" -#include "../../../OrthancServer/DatabaseWrapperBase.h" -#include "../../Engine/PluginsEnumerations.h" - -#include <memory> - -class Database : public OrthancPlugins::IDatabaseBackend -{ -private: - class SignalRemainingAncestor; - - std::string path_; - Orthanc::SQLite::Connection db_; - Orthanc::DatabaseWrapperBase base_; - SignalRemainingAncestor* signalRemainingAncestor_; - - std::auto_ptr<Orthanc::SQLite::Transaction> transaction_; - -public: - Database(const std::string& path); - - virtual void Open(); - - virtual void Close(); - - virtual void AddAttachment(int64_t id, - const OrthancPluginAttachment& attachment); - - virtual void AttachChild(int64_t parent, - int64_t child) - { - base_.AttachChild(parent, child); - } - - virtual void ClearChanges() - { - db_.Execute("DELETE FROM Changes"); - } - - virtual void ClearExportedResources() - { - db_.Execute("DELETE FROM ExportedResources"); - } - - virtual int64_t CreateResource(const char* publicId, - OrthancPluginResourceType type) - { - return base_.CreateResource(publicId, Orthanc::Plugins::Convert(type)); - } - - virtual void DeleteAttachment(int64_t id, - int32_t attachment) - { - base_.DeleteAttachment(id, static_cast<Orthanc::FileContentType>(attachment)); - } - - virtual void DeleteMetadata(int64_t id, - int32_t metadataType) - { - base_.DeleteMetadata(id, static_cast<Orthanc::MetadataType>(metadataType)); - } - - virtual void DeleteResource(int64_t id); - - virtual void GetAllInternalIds(std::list<int64_t>& target, - OrthancPluginResourceType resourceType) - { - base_.GetAllInternalIds(target, Orthanc::Plugins::Convert(resourceType)); - } - - virtual void GetAllPublicIds(std::list<std::string>& target, - OrthancPluginResourceType resourceType) - { - base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType)); - } - - virtual void GetAllPublicIds(std::list<std::string>& target, - OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) - { - base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType), since, limit); - } - - virtual void GetChanges(bool& done /*out*/, - int64_t since, - uint32_t maxResults); - - virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/, - int64_t id) - { - base_.GetChildrenInternalId(target, id); - } - - virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/, - int64_t id) - { - base_.GetChildrenPublicId(target, id); - } - - virtual void GetExportedResources(bool& done /*out*/, - int64_t since, - uint32_t maxResults); - - virtual void GetLastChange(); - - virtual void GetLastExportedResource(); - - virtual void GetMainDicomTags(int64_t id); - - virtual std::string GetPublicId(int64_t resourceId); - - virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) - { - return base_.GetResourceCount(Orthanc::Plugins::Convert(resourceType)); - } - - virtual OrthancPluginResourceType GetResourceType(int64_t resourceId); - - virtual uint64_t GetTotalCompressedSize() - { - return base_.GetTotalCompressedSize(); - } - - virtual uint64_t GetTotalUncompressedSize() - { - return base_.GetTotalUncompressedSize(); - } - - virtual bool IsExistingResource(int64_t internalId) - { - return base_.IsExistingResource(internalId); - } - - virtual bool IsProtectedPatient(int64_t internalId) - { - return base_.IsProtectedPatient(internalId); - } - - virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/, - int64_t id); - - virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/, - int64_t id); - - virtual void LogChange(const OrthancPluginChange& change); - - virtual void LogExportedResource(const OrthancPluginExportedResource& resource); - - virtual bool LookupAttachment(int64_t id, - int32_t contentType); - - virtual bool LookupGlobalProperty(std::string& target /*out*/, - int32_t property) - { - return base_.LookupGlobalProperty(target, static_cast<Orthanc::GlobalProperty>(property)); - } - - virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, - OrthancPluginResourceType level, - uint16_t group, - uint16_t element, - OrthancPluginIdentifierConstraint constraint, - const char* value) - { - base_.LookupIdentifier(target, Orthanc::Plugins::Convert(level), - Orthanc::DicomTag(group, element), - Orthanc::Plugins::Convert(constraint), value); - } - - virtual bool LookupMetadata(std::string& target /*out*/, - int64_t id, - int32_t metadataType) - { - return base_.LookupMetadata(target, id, static_cast<Orthanc::MetadataType>(metadataType)); - } - - virtual bool LookupParent(int64_t& parentId /*out*/, - int64_t resourceId); - - virtual bool LookupResource(int64_t& id /*out*/, - OrthancPluginResourceType& type /*out*/, - const char* publicId); - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) - { - return base_.SelectPatientToRecycle(internalId); - } - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/, - int64_t patientIdToAvoid) - { - return base_.SelectPatientToRecycle(internalId, patientIdToAvoid); - } - - - virtual void SetGlobalProperty(int32_t property, - const char* value) - { - base_.SetGlobalProperty(static_cast<Orthanc::GlobalProperty>(property), value); - } - - virtual void SetMainDicomTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) - { - base_.SetMainDicomTag(id, Orthanc::DicomTag(group, element), value); - } - - virtual void SetIdentifierTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) - { - base_.SetIdentifierTag(id, Orthanc::DicomTag(group, element), value); - } - - virtual void SetMetadata(int64_t id, - int32_t metadataType, - const char* value) - { - base_.SetMetadata(id, static_cast<Orthanc::MetadataType>(metadataType), value); - } - - virtual void SetProtectedPatient(int64_t internalId, - bool isProtected) - { - base_.SetProtectedPatient(internalId, isProtected); - } - - virtual void StartTransaction(); - - virtual void RollbackTransaction(); - - virtual void CommitTransaction(); - - virtual uint32_t GetDatabaseVersion(); - - virtual void UpgradeDatabase(uint32_t targetVersion, - OrthancPluginStorageArea* storageArea); - - virtual void ClearMainDicomTags(int64_t internalId) - { - base_.ClearMainDicomTags(internalId); - } -};
--- a/Plugins/Samples/DatabasePlugin/Plugin.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "Database.h" - -#include <memory> -#include <iostream> -#include <boost/algorithm/string/predicate.hpp> - -static OrthancPluginContext* context_ = NULL; -static std::auto_ptr<OrthancPlugins::IDatabaseBackend> backend_; - - -extern "C" -{ - ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) - { - context_ = c; - OrthancPluginLogWarning(context_, "Sample plugin is initializing"); - - /* 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; - } - - std::string path = "SampleDatabase.sqlite"; - uint32_t argCount = OrthancPluginGetCommandLineArgumentsCount(context_); - for (uint32_t i = 0; i < argCount; i++) - { - char* tmp = OrthancPluginGetCommandLineArgument(context_, i); - std::string argument(tmp); - OrthancPluginFreeString(context_, tmp); - - if (boost::starts_with(argument, "--database=")) - { - path = argument.substr(11); - } - } - - std::string s = "Using the following SQLite database: " + path; - OrthancPluginLogWarning(context_, s.c_str()); - - backend_.reset(new Database(path)); - OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_); - - return 0; - } - - ORTHANC_PLUGINS_API void OrthancPluginFinalize() - { - backend_.reset(NULL); - } - - ORTHANC_PLUGINS_API const char* OrthancPluginGetName() - { - return "sample-database"; - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() - { - return "1.0"; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.8) + +project(GdcmDecoder) + +SET(GDCM_DECODER_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_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/BoostConfiguration.cmake) + +find_package(GDCM REQUIRED) +if (GDCM_FOUND) + include(${GDCM_USE_FILE}) + set(GDCM_LIBRARIES gdcmCommon gdcmMSFF) +else(GDCM_FOUND) + message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?") +endif(GDCM_FOUND) + +add_definitions(-DGDCM_DECODER_VERSION="${GDCM_DECODER_VERSION}") + +add_library(GdcmDecoder SHARED + ${BOOST_SOURCES} + GdcmDecoderCache.cpp + GdcmImageDecoder.cpp + OrthancImageWrapper.cpp + Plugin.cpp + ) + +target_link_libraries(GdcmDecoder ${GDCM_LIBRARIES})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "GdcmDecoderCache.h" + +#include "OrthancImageWrapper.h" + +namespace OrthancPlugins +{ + std::string GdcmDecoderCache::ComputeMd5(OrthancPluginContext* context, + const void* dicom, + size_t size) + { + std::string result; + + char* md5 = OrthancPluginComputeMd5(context, dicom, size); + + if (md5 == NULL) + { + throw std::runtime_error("Cannot compute MD5 hash"); + } + + bool ok = false; + try + { + result.assign(md5); + ok = true; + } + catch (...) + { + } + + OrthancPluginFreeString(context, md5); + + if (!ok) + { + throw std::runtime_error("Not enough memory"); + } + else + { + return result; + } + } + + + OrthancImageWrapper* GdcmDecoderCache::Decode(OrthancPluginContext* context, + const void* dicom, + const uint32_t size, + uint32_t frameIndex) + { + std::string md5 = ComputeMd5(context, dicom, size); + + // First check whether the previously decoded image is the same + // as this one + { + boost::mutex::scoped_lock lock(mutex_); + + if (decoder_.get() != NULL && + size_ == size && + md5_ == md5) + { + // This is the same image: Reuse the previous decoding + return new OrthancImageWrapper(context, decoder_->Decode(context, frameIndex)); + } + } + + // 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))); + + { + // Cache the newly created decoder for further use + boost::mutex::scoped_lock lock(mutex_); + decoder_ = decoder; + size_ = size; + md5_ = md5; + } + + return image.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/>. + **/ + + +#pragma once + +#include "GdcmImageDecoder.h" +#include "OrthancImageWrapper.h" + +#include <boost/thread.hpp> + + +namespace OrthancPlugins +{ + class GdcmDecoderCache : public boost::noncopyable + { + private: + boost::mutex mutex_; + std::auto_ptr<OrthancPlugins::GdcmImageDecoder> decoder_; + size_t size_; + std::string md5_; + + static std::string ComputeMd5(OrthancPluginContext* context, + const void* dicom, + size_t size); + + public: + GdcmDecoderCache() : size_(0) + { + } + + OrthancImageWrapper* Decode(OrthancPluginContext* context, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,405 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "GdcmImageDecoder.h" + +#include "OrthancImageWrapper.h" + +#include <gdcmImageReader.h> +#include <gdcmImageApplyLookupTable.h> +#include <gdcmImageChangePlanarConfiguration.h> +#include <gdcmImageChangePhotometricInterpretation.h> +#include <stdexcept> +#include <boost/iostreams/stream.hpp> +#include <boost/iostreams/device/array.hpp> + + +namespace OrthancPlugins +{ + struct GdcmImageDecoder::PImpl + { + const void* dicom_; + 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::string decoded_; + + PImpl(const void* dicom, + size_t size) : + dicom_(dicom), + size_(size) + { + } + + + const gdcm::DataSet& GetDataSet() const + { + return reader_.GetFile().GetDataSet(); + } + + + const gdcm::Image& GetImage() const + { + if (interleaved_.get() != NULL) + { + return interleaved_->GetOutput(); + } + + if (lut_.get() != NULL) + { + return lut_->GetOutput(); + } + + if (photometric_.get() != NULL) + { + return photometric_->GetOutput(); + } + + return reader_.GetImage(); + } + + + void Decode() + { + // Change photometric interpretation or apply LUT, if required + { + const gdcm::Image& image = GetImage(); + if (image.GetPixelFormat().GetSamplesPerPixel() == 1 && + image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::PALETTE_COLOR) + { + lut_.reset(new gdcm::ImageApplyLookupTable()); + lut_->SetInput(image); + if (!lut_->Apply()) + { + throw std::runtime_error( "GDCM cannot apply the lookup table"); + } + } + else if (image.GetPixelFormat().GetSamplesPerPixel() == 1) + { + if (image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME1 && + image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2) + { + photometric_.reset(new gdcm::ImageChangePhotometricInterpretation()); + photometric_->SetInput(image); + photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2); + if (!photometric_->Change() || + GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::MONOCHROME2) + { + throw std::runtime_error("GDCM cannot change the photometric interpretation"); + } + } + } + else + { + if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && + image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB && + image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::YBR_FULL && + (image.GetTransferSyntax() != gdcm::TransferSyntax::JPEG2000Lossless || + image.GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::YBR_RCT)) + { + photometric_.reset(new gdcm::ImageChangePhotometricInterpretation()); + photometric_->SetInput(image); + photometric_->SetPhotometricInterpretation(gdcm::PhotometricInterpretation::RGB); + if (!photometric_->Change() || + GetImage().GetPhotometricInterpretation() != gdcm::PhotometricInterpretation::RGB) + { + throw std::runtime_error("GDCM cannot change the photometric interpretation"); + } + } + } + } + + // Possibly convert planar configuration to interleaved + { + const gdcm::Image& image = GetImage(); + if (image.GetPlanarConfiguration() != 0 && + image.GetPixelFormat().GetSamplesPerPixel() != 1) + { + interleaved_.reset(new gdcm::ImageChangePlanarConfiguration()); + interleaved_->SetInput(image); + if (!interleaved_->Change() || + GetImage().GetPlanarConfiguration() != 0) + { + throw std::runtime_error("GDCM cannot change the planar configuration to interleaved"); + } + } + } + } + }; + + GdcmImageDecoder::GdcmImageDecoder(const void* dicom, + size_t size) : + pimpl_(new PImpl(dicom, size)) + { + // Setup a stream to the memory buffer + using namespace boost::iostreams; + basic_array_source<char> source(reinterpret_cast<const char*>(dicom), size); + stream<basic_array_source<char> > stream(source); + + // Parse the DICOM instance using GDCM + pimpl_->reader_.SetStream(stream); + if (!pimpl_->reader_.Read()) + { + throw std::runtime_error("Bad file format"); + } + + pimpl_->Decode(); + } + + + OrthancPluginPixelFormat GdcmImageDecoder::GetFormat() const + { + const gdcm::Image& image = pimpl_->GetImage(); + + if (image.GetPixelFormat().GetSamplesPerPixel() == 1 && + (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME1 || + image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::MONOCHROME2)) + { + switch (image.GetPixelFormat()) + { + case gdcm::PixelFormat::UINT16: + return OrthancPluginPixelFormat_Grayscale16; + + case gdcm::PixelFormat::INT16: + return OrthancPluginPixelFormat_SignedGrayscale16; + + case gdcm::PixelFormat::UINT8: + return OrthancPluginPixelFormat_Grayscale8; + + default: + throw std::runtime_error("Unsupported pixel format"); + } + } + else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && + (image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::RGB || + image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::YBR_FULL || + image.GetPhotometricInterpretation() == gdcm::PhotometricInterpretation::YBR_RCT)) + { + switch (image.GetPixelFormat()) + { + case gdcm::PixelFormat::UINT8: + return OrthancPluginPixelFormat_RGB24; + + case gdcm::PixelFormat::UINT16: +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 3, 1) + return OrthancPluginPixelFormat_RGB48; +#else + throw std::runtime_error("RGB48 pixel format is only supported if compiled against Orthanc SDK >= 1.3.1"); +#endif + + default: + break; + } + } + + throw std::runtime_error("Unsupported pixel format"); + } + + + unsigned int GdcmImageDecoder::GetWidth() const + { + return pimpl_->GetImage().GetColumns(); + } + + + unsigned int GdcmImageDecoder::GetHeight() const + { + return pimpl_->GetImage().GetRows(); + } + + + unsigned int GdcmImageDecoder::GetFramesCount() const + { + return pimpl_->GetImage().GetDimension(2); + } + + + size_t GdcmImageDecoder::GetBytesPerPixel(OrthancPluginPixelFormat format) + { + switch (format) + { + case OrthancPluginPixelFormat_Grayscale8: + return 1; + + case OrthancPluginPixelFormat_Grayscale16: + case OrthancPluginPixelFormat_SignedGrayscale16: + return 2; + + case OrthancPluginPixelFormat_RGB24: + return 3; + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 3, 1) + case OrthancPluginPixelFormat_RGB48: + return 6; +#endif + + default: + throw std::runtime_error("Unsupport pixel format"); + } + } + + static void ConvertYbrToRgb(uint8_t rgb[3], + const uint8_t ybr[3]) + { + // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2 + // https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion + + // TODO - Check out the outcome of Mathieu's discussion about + // truncation of YCbCr-to-RGB conversion: + // https://groups.google.com/forum/#!msg/comp.protocols.dicom/JHuGeyWbTz8/ARoTWrJzAQAJ + + const float Y = ybr[0]; + const float Cb = ybr[1]; + const float Cr = ybr[2]; + + const float result[3] = { + Y + 1.402f * (Cr - 128.0f), + Y - 0.344136f * (Cb - 128.0f) - 0.714136f * (Cr - 128.0f), + Y + 1.772f * (Cb - 128.0f) + }; + + for (uint8_t i = 0; i < 3 ; i++) + { + if (result[i] < 0) + { + rgb[i] = 0; + } + else if (result[i] > 255) + { + rgb[i] = 255; + } + else + { + rgb[i] = static_cast<uint8_t>(result[i]); + } + } + } + + + static void FixPhotometricInterpretation(OrthancImageWrapper& image, + gdcm::PhotometricInterpretation interpretation) + { + switch (interpretation) + { + case gdcm::PhotometricInterpretation::MONOCHROME1: + case gdcm::PhotometricInterpretation::MONOCHROME2: + case gdcm::PhotometricInterpretation::RGB: + return; + + case gdcm::PhotometricInterpretation::YBR_FULL: + { + // Fix for Osimis issue WVB-319: Some images are not loading in US_MF + + uint32_t width = image.GetWidth(); + uint32_t height = image.GetHeight(); + uint32_t pitch = image.GetPitch(); + uint8_t* buffer = reinterpret_cast<uint8_t*>(image.GetBuffer()); + + if (image.GetFormat() != OrthancPluginPixelFormat_RGB24 || + pitch < 3 * width) + { + throw std::runtime_error("Internal error"); + } + + for (uint32_t y = 0; y < height; y++) + { + uint8_t* p = buffer + y * pitch; + for (uint32_t x = 0; x < width; x++, p += 3) + { + const uint8_t ybr[3] = { p[0], p[1], p[2] }; + uint8_t rgb[3]; + ConvertYbrToRgb(rgb, ybr); + p[0] = rgb[0]; + p[1] = rgb[1]; + p[2] = rgb[2]; + } + } + + return; + } + + default: + throw std::runtime_error("Unsupported output photometric interpretation"); + } + } + + + OrthancPluginImage* GdcmImageDecoder::Decode(OrthancPluginContext* context, + unsigned int frameIndex) const + { + unsigned int frames = GetFramesCount(); + unsigned int width = GetWidth(); + unsigned int height = GetHeight(); + OrthancPluginPixelFormat format = GetFormat(); + size_t bpp = GetBytesPerPixel(format); + + if (frameIndex >= frames) + { + throw std::runtime_error("Inexistent frame index"); + } + + std::string& decoded = pimpl_->decoded_; + OrthancImageWrapper target(context, format, width, height); + + if (width == 0 || + height == 0) + { + return target.Release(); + } + + if (decoded.empty()) + { + decoded.resize(pimpl_->GetImage().GetBufferLength()); + pimpl_->GetImage().GetBuffer(&decoded[0]); + } + + const void* sourceBuffer = &decoded[0]; + + if (target.GetPitch() == bpp * width && + frames == 1) + { + assert(decoded.size() == target.GetPitch() * target.GetHeight()); + memcpy(target.GetBuffer(), sourceBuffer, decoded.size()); + } + else + { + size_t targetPitch = target.GetPitch(); + size_t sourcePitch = width * bpp; + + const char* a = &decoded[sourcePitch * height * frameIndex]; + char* b = target.GetBuffer(); + + for (uint32_t y = 0; y < height; y++) + { + memcpy(b, a, sourcePitch); + a += sourcePitch; + b += targetPitch; + } + } + + FixPhotometricInterpretation(target, pimpl_->GetImage().GetPhotometricInterpretation()); + + return target.Release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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/>. + **/ + + +#pragma once + +#include <orthanc/OrthancCPlugin.h> +#include <stdint.h> +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> + + +// This is for compatibility with Orthanc SDK <= 1.3.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 && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + +namespace OrthancPlugins +{ + class GdcmImageDecoder : public boost::noncopyable + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + public: + GdcmImageDecoder(const void* dicom, + size_t size); + + OrthancPluginPixelFormat GetFormat() const; + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + unsigned int GetFramesCount() const; + + static size_t GetBytesPerPixel(OrthancPluginPixelFormat format); + + OrthancPluginImage* Decode(OrthancPluginContext* context, + unsigned int frameIndex) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,100 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "OrthancImageWrapper.h" + +#include <stdexcept> + +namespace OrthancPlugins +{ + OrthancImageWrapper::OrthancImageWrapper(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) : + context_(context) + { + image_ = OrthancPluginCreateImage(context_, format, width, height); + if (image_ == NULL) + { + throw std::runtime_error("Cannot create an image"); + } + } + + + OrthancImageWrapper::OrthancImageWrapper(OrthancPluginContext* context, + OrthancPluginImage* image) : + context_(context), + image_(image) + { + if (image_ == NULL) + { + throw std::runtime_error("Invalid image returned by the core of Orthanc"); + } + } + + + + OrthancImageWrapper::~OrthancImageWrapper() + { + if (image_ != NULL) + { + OrthancPluginFreeImage(context_, image_); + } + } + + + OrthancPluginImage* OrthancImageWrapper::Release() + { + OrthancPluginImage* tmp = image_; + image_ = NULL; + return tmp; + } + + + uint32_t OrthancImageWrapper::GetWidth() + { + return OrthancPluginGetImageWidth(context_, image_); + } + + + uint32_t OrthancImageWrapper::GetHeight() + { + return OrthancPluginGetImageHeight(context_, image_); + } + + + uint32_t OrthancImageWrapper::GetPitch() + { + return OrthancPluginGetImagePitch(context_, image_); + } + + + OrthancPluginPixelFormat OrthancImageWrapper::GetFormat() + { + return OrthancPluginGetImagePixelFormat(context_, image_); + } + + + char* OrthancImageWrapper::GetBuffer() + { + return reinterpret_cast<char*>(OrthancPluginGetImageBuffer(context_, image_)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/>. + **/ + + +#pragma once + +#include <orthanc/OrthancCPlugin.h> + +#include "GdcmImageDecoder.h" + +namespace OrthancPlugins +{ + class OrthancImageWrapper + { + private: + OrthancPluginContext* context_; + OrthancPluginImage* image_; + + public: + OrthancImageWrapper(OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height); + + OrthancImageWrapper(OrthancPluginContext* context, + OrthancPluginImage* image); // Takes ownership + + ~OrthancImageWrapper(); + + OrthancPluginContext* GetContext() + { + return context_; + } + + OrthancPluginImage* Release(); + + uint32_t GetWidth(); + + uint32_t GetHeight(); + + uint32_t GetPitch(); + + OrthancPluginPixelFormat GetFormat(); + + char* GetBuffer(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,107 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "GdcmDecoderCache.h" +#include "OrthancImageWrapper.h" + +#include <orthanc/OrthancCPlugin.h> + +static OrthancPluginContext* context_ = NULL; +static OrthancPlugins::GdcmDecoderCache cache_; + + +static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex) +{ + try + { + std::auto_ptr<OrthancPlugins::OrthancImageWrapper> image; + +#if 0 + // Do not use the cache + OrthancPlugins::GdcmImageDecoder decoder(dicom, size); + image.reset(new OrthancPlugins::OrthancImageWrapper(context_, decoder.Decode(context_, frameIndex))); +#else + image.reset(cache_.Decode(context_, dicom, size, frameIndex)); +#endif + + *target = image->Release(); + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + *target = NULL; + + std::string s = "Cannot decode image using GDCM: " + std::string(e.what()); + OrthancPluginLogInfo(context_, s.c_str()); + return OrthancPluginErrorCode_Plugin; + } +} + + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + context_ = context; + OrthancPluginLogWarning(context_, "Initializing the advanced decoder of medical images using GDCM"); + + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context_) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context_->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + OrthancPluginSetDescription(context_, "Advanced decoder of medical images using GDCM."); + OrthancPluginRegisterDecodeImageCallback(context_, DecodeImageCallback); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "gdcm-decoder"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return GDCM_DECODER_VERSION; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/GdcmDecoder/README Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,6 @@ +This sample shows how to replace the decoder of DICOM images that is +built in Orthanc, by the GDCM library. + +A production-ready version of this sample, is available in the +offical Web viewer plugin: +http://www.orthanc-server.com/static.php?page=web-viewer
--- a/Plugins/Samples/GdcmDecoding/CMakeLists.txt Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -project(GdcmDecoding) - -SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") -SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") - -SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") -SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") - -set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) -include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) - -include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) - -find_package(GDCM REQUIRED) -if (GDCM_FOUND) - include(${GDCM_USE_FILE}) - set(GDCM_LIBRARIES gdcmCommon gdcmMSFF) -else(GDCM_FOUND) - message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?") -endif(GDCM_FOUND) - -add_library(GdcmDecoding SHARED - Plugin.cpp - OrthancContext.cpp - - # Sources from Orthanc - ${GOOGLE_LOG_SOURCES} - ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp - ${ORTHANC_ROOT}/Core/Enumerations.cpp - ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp - ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp - ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp - ${ORTHANC_ROOT}/Core/Toolbox.cpp - ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp - ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c - ${JSONCPP_SOURCES} - ${THIRD_PARTY_SOURCES} - ) - -target_link_libraries(GdcmDecoding ${GDCM_LIBRARIES})
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,151 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 "OrthancContext.h" - -#include <stdexcept> - - -void OrthancContext::Check() -{ - if (context_ == NULL) - { - throw std::runtime_error("The Orthanc plugin context is not initialized"); - } -} - - -OrthancContext& OrthancContext::GetInstance() -{ - static OrthancContext instance; - return instance; -} - - -void OrthancContext::ExtractGetArguments(Arguments& arguments, - const OrthancPluginHttpRequest& request) -{ - Check(); - arguments.clear(); - - for (uint32_t i = 0; i < request.getCount; i++) - { - arguments[request.getKeys[i]] = request.getValues[i]; - } -} - - -void OrthancContext::LogError(const std::string& s) -{ - Check(); - OrthancPluginLogError(context_, s.c_str()); -} - - -void OrthancContext::LogWarning(const std::string& s) -{ - Check(); - OrthancPluginLogWarning(context_, s.c_str()); -} - - -void OrthancContext::LogInfo(const std::string& s) -{ - Check(); - OrthancPluginLogInfo(context_, s.c_str()); -} - - -void OrthancContext::Register(const std::string& uri, - OrthancPluginRestCallback callback) -{ - Check(); - OrthancPluginRegisterRestCallback(context_, uri.c_str(), callback); -} - - -void OrthancContext::GetDicomForInstance(std::string& result, - const std::string& instanceId) -{ - Check(); - OrthancPluginMemoryBuffer buffer; - - if (OrthancPluginGetDicomForInstance(context_, &buffer, instanceId.c_str())) - { - throw std::runtime_error("No DICOM instance with Orthanc ID: " + instanceId); - } - - if (buffer.size == 0) - { - result.clear(); - } - else - { - result.assign(reinterpret_cast<char*>(buffer.data), buffer.size); - } - - OrthancPluginFreeMemoryBuffer(context_, &buffer); -} - - -void OrthancContext::CompressAndAnswerPngImage(OrthancPluginRestOutput* output, - const Orthanc::ImageAccessor& accessor) -{ - Check(); - - OrthancPluginPixelFormat format; - switch (accessor.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - format = OrthancPluginPixelFormat_Grayscale8; - break; - - case Orthanc::PixelFormat_Grayscale16: - format = OrthancPluginPixelFormat_Grayscale16; - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - format = OrthancPluginPixelFormat_SignedGrayscale16; - break; - - case Orthanc::PixelFormat_RGB24: - format = OrthancPluginPixelFormat_RGB24; - break; - - case Orthanc::PixelFormat_RGBA32: - format = OrthancPluginPixelFormat_RGBA32; - break; - - default: - throw std::runtime_error("Unsupported pixel format"); - } - - OrthancPluginCompressAndAnswerPngImage(context_, output, format, accessor.GetWidth(), - accessor.GetHeight(), accessor.GetPitch(), accessor.GetConstBuffer()); -} - - - -void OrthancContext::Redirect(OrthancPluginRestOutput* output, - const std::string& s) -{ - Check(); - OrthancPluginRedirect(context_, output, s.c_str()); -}
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <orthanc/OrthancCPlugin.h> - -#include "../../../Core/Images/ImageBuffer.h" - -#include <map> -#include <string> -#include <boost/noncopyable.hpp> - - -class OrthancContext : public boost::noncopyable -{ -private: - OrthancPluginContext* context_; - - OrthancContext() : context_(NULL) - { - } - - void Check(); - -public: - typedef std::map<std::string, std::string> Arguments; - - static OrthancContext& GetInstance(); - - void Initialize(OrthancPluginContext* context) - { - context_ = context; - } - - void Finalize() - { - context_ = NULL; - } - - void ExtractGetArguments(Arguments& arguments, - const OrthancPluginHttpRequest& request); - - void LogError(const std::string& s); - - void LogWarning(const std::string& s); - - void LogInfo(const std::string& s); - - void Register(const std::string& uri, - OrthancPluginRestCallback callback); - - void GetDicomForInstance(std::string& result, - const std::string& instanceId); - - void CompressAndAnswerPngImage(OrthancPluginRestOutput* output, - const Orthanc::ImageAccessor& accessor); - - void Redirect(OrthancPluginRestOutput* output, - const std::string& s); -};
--- a/Plugins/Samples/GdcmDecoding/Plugin.cpp Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,264 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 <map> -#include <string> -#include <stdexcept> -#include <sstream> -#include <boost/lexical_cast.hpp> - -#include "OrthancContext.h" -#include "../../../Core/Images/ImageProcessing.h" - -#include <gdcmReader.h> -#include <gdcmImageReader.h> -#include <gdcmImageChangePlanarConfiguration.h> - - -static void AnswerUnsupportedImage(OrthancPluginRestOutput* output) -{ - OrthancContext::GetInstance().Redirect(output, "/app/images/unsupported.png"); -} - - -static bool GetOrthancPixelFormat(Orthanc::PixelFormat& format, - const gdcm::Image& image) -{ - if (image.GetPlanarConfiguration() != 0 && - image.GetPixelFormat().GetSamplesPerPixel() != 1) - { - OrthancContext::GetInstance().LogError("Planar configurations are not supported"); - return false; - } - - if (image.GetPixelFormat().GetSamplesPerPixel() == 1) - { - switch (image.GetPixelFormat().GetScalarType()) - { - case gdcm::PixelFormat::UINT8: - format = Orthanc::PixelFormat_Grayscale8; - return true; - - case gdcm::PixelFormat::UINT16: - format = Orthanc::PixelFormat_Grayscale16; - return true; - - case gdcm::PixelFormat::INT16: - format = Orthanc::PixelFormat_SignedGrayscale16; - return true; - - default: - return false; - } - } - else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 && - image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8) - { - format = Orthanc::PixelFormat_RGB24; - return true; - } - else if (image.GetPixelFormat().GetSamplesPerPixel() == 4 && - image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8) - { - format = Orthanc::PixelFormat_RGBA32; - return true; - } - - return false; -} - - -ORTHANC_PLUGINS_API OrthancPluginErrorCode DecodeImage(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - std::string instance(request->groups[0]); - std::string outputFormat(request->groups[1]); - OrthancContext::GetInstance().LogWarning("Using GDCM to decode instance " + instance); - - // Download the request DICOM instance from Orthanc into a memory buffer - std::string dicom; - OrthancContext::GetInstance().GetDicomForInstance(dicom, instance); - - // Prepare a memory stream over the DICOM instance - std::stringstream stream(dicom); - - // Parse the DICOM instance using GDCM - gdcm::ImageReader imageReader; - imageReader.SetStream(stream); - if (!imageReader.Read()) - { - OrthancContext::GetInstance().LogError("GDCM cannot extract an image from this DICOM instance"); - AnswerUnsupportedImage(output); - return OrthancPluginErrorCode_Success; - } - - gdcm::Image& image = imageReader.GetImage(); - - - // Log information about the decoded image - char tmp[1024]; - sprintf(tmp, "Image format: %dx%d %s with %d color channel(s)", image.GetRows(), image.GetColumns(), - image.GetPixelFormat().GetScalarTypeAsString(), image.GetPixelFormat().GetSamplesPerPixel()); - OrthancContext::GetInstance().LogWarning(tmp); - - - // Convert planar configuration - gdcm::ImageChangePlanarConfiguration planar; - if (image.GetPlanarConfiguration() != 0 && - image.GetPixelFormat().GetSamplesPerPixel() != 1) - { - OrthancContext::GetInstance().LogWarning("Converting planar configuration to interleaved"); - planar.SetInput(imageReader.GetImage()); - planar.Change(); - image = planar.GetOutput(); - } - - - // Create a read-only accessor to the bitmap decoded by GDCM - Orthanc::PixelFormat format; - if (!GetOrthancPixelFormat(format, image)) - { - OrthancContext::GetInstance().LogError("This sample plugin does not support this image format"); - AnswerUnsupportedImage(output); - return OrthancPluginErrorCode_Success; - } - - Orthanc::ImageAccessor decodedImage; - std::vector<char> decodedBuffer(image.GetBufferLength()); - - if (decodedBuffer.size()) - { - image.GetBuffer(&decodedBuffer[0]); - unsigned int pitch = image.GetColumns() * ::Orthanc::GetBytesPerPixel(format); - decodedImage.AssignWritable(format, image.GetColumns(), image.GetRows(), pitch, &decodedBuffer[0]); - } - else - { - // Empty image - decodedImage.AssignWritable(format, 0, 0, 0, NULL); - } - - - // Convert the pixel format from GDCM to the format requested by the REST query - Orthanc::ImageBuffer converted; - converted.SetWidth(decodedImage.GetWidth()); - converted.SetHeight(decodedImage.GetHeight()); - - if (outputFormat == "preview") - { - if (format == Orthanc::PixelFormat_RGB24 || - format == Orthanc::PixelFormat_RGBA32) - { - // Do not rescale color image - converted.SetFormat(Orthanc::PixelFormat_RGB24); - } - else - { - converted.SetFormat(Orthanc::PixelFormat_Grayscale8); - - // Rescale the image to the [0,255] range - int64_t a, b; - Orthanc::ImageProcessing::GetMinMaxValue(a, b, decodedImage); - - float offset = -a; - float scaling = 255.0f / static_cast<float>(b - a); - Orthanc::ImageProcessing::ShiftScale(decodedImage, offset, scaling); - } - } - else - { - if (format == Orthanc::PixelFormat_RGB24 || - format == Orthanc::PixelFormat_RGBA32) - { - // Do not convert color images to grayscale values (this is Orthanc convention) - AnswerUnsupportedImage(output); - return OrthancPluginErrorCode_Success; - } - - if (outputFormat == "image-uint8") - { - converted.SetFormat(Orthanc::PixelFormat_Grayscale8); - } - else if (outputFormat == "image-uint16") - { - converted.SetFormat(Orthanc::PixelFormat_Grayscale16); - } - else if (outputFormat == "image-int16") - { - converted.SetFormat(Orthanc::PixelFormat_SignedGrayscale16); - } - else - { - OrthancContext::GetInstance().LogError("Unknown output format: " + outputFormat); - AnswerUnsupportedImage(output); - return OrthancPluginErrorCode_Success; - } - } - - Orthanc::ImageAccessor convertedAccessor(converted.GetAccessor()); - Orthanc::ImageProcessing::Convert(convertedAccessor, decodedImage); - - // Compress the converted image as a PNG file - OrthancContext::GetInstance().CompressAndAnswerPngImage(output, convertedAccessor); - - return OrthancPluginErrorCode_Success; // Success -} - - -extern "C" -{ - ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) - { - OrthancContext::GetInstance().Initialize(context); - OrthancContext::GetInstance().LogWarning("Initializing GDCM decoding"); - - // Check the version of the Orthanc core - if (OrthancPluginCheckVersion(context) == 0) - { - OrthancContext::GetInstance().LogError( - "Your version of Orthanc (" + std::string(context->orthancVersion) + - ") must be above " + boost::lexical_cast<std::string>(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + - "." + boost::lexical_cast<std::string>(ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + - "." + boost::lexical_cast<std::string>(ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) + - " to run this plugin"); - return -1; - } - - OrthancContext::GetInstance().Register("/instances/([^/]+)/(preview|image-uint8|image-uint16|image-int16)", DecodeImage); - return 0; - } - - ORTHANC_PLUGINS_API void OrthancPluginFinalize() - { - OrthancContext::GetInstance().LogWarning("Finalizing GDCM decoding"); - OrthancContext::GetInstance().Finalize(); - } - - ORTHANC_PLUGINS_API const char* OrthancPluginGetName() - { - return "gdcm-decoding"; - } - - ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() - { - return "1.0"; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ModalityWorklists/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8) + +project(ModalityWorklists) + +SET(MODALITY_WORKLISTS_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(ModalityWorklists SHARED + Plugin.cpp + ../Common/OrthancPluginCppWrapper.cpp + ${JSONCPP_SOURCES} + ${BOOST_SOURCES} + ) + +message("Setting the version of the plugin to ${MODALITY_WORKLISTS_VERSION}") +add_definitions( + -DMODALITY_WORKLISTS_VERSION="${MODALITY_WORKLISTS_VERSION}" + ) + +set_target_properties(ModalityWorklists PROPERTIES + VERSION ${MODALITY_WORKLISTS_VERSION} + SOVERSION ${MODALITY_WORKLISTS_VERSION}) + +install( + TARGETS ModalityWorklists + 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/ModalityWorklists/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,268 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 <boost/filesystem.hpp> +#include <json/value.h> +#include <json/reader.h> +#include <string.h> +#include <iostream> +#include <algorithm> + +static OrthancPluginContext* context_ = NULL; +static std::string folder_; +static bool filterIssuerAet_ = false; + +/** + * This is the main function for matching a DICOM worklist against a query. + **/ +static bool MatchWorklist(OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const OrthancPlugins::FindMatcher& matcher, + const std::string& path) +{ + OrthancPlugins::MemoryBuffer dicom(context_); + dicom.ReadFile(path); + + if (matcher.IsMatch(dicom)) + { + // This DICOM file matches the worklist query, add it to the answers + OrthancPluginErrorCode code = OrthancPluginWorklistAddAnswer + (context_, answers, query, dicom.GetData(), dicom.GetSize()); + + if (code != OrthancPluginErrorCode_Success) + { + OrthancPlugins::LogError(context_, "Error while adding an answer to a worklist request"); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + + return true; + } + + return false; +} + + +static OrthancPlugins::FindMatcher* CreateMatcher(const OrthancPluginWorklistQuery* query, + const char* issuerAet) +{ + // Extract the DICOM instance underlying the C-Find query + OrthancPlugins::MemoryBuffer dicom(context_); + dicom.GetDicomQuery(query); + + // Convert the DICOM as JSON, and dump it to the user in "--verbose" mode + Json::Value json; + dicom.DicomToJson(json, OrthancPluginDicomToJsonFormat_Short, + static_cast<OrthancPluginDicomToJsonFlags>(0), 0); + + OrthancPlugins::LogInfo(context_, "Received worklist query from remote modality " + + std::string(issuerAet) + ":\n" + json.toStyledString()); + + if (!filterIssuerAet_) + { + return new OrthancPlugins::FindMatcher(context_, query); + } + else + { + // Alternative sample showing how to fine-tune an incoming C-Find + // request, before matching it against the worklist database. The + // code below will restrict the original DICOM request by + // requesting the ScheduledStationAETitle to correspond to the AET + // of the C-Find issuer. This code will make the integration test + // "test_filter_issuer_aet" succeed (cf. the orthanc-tests repository). + + static const char* SCHEDULED_PROCEDURE_STEP_SEQUENCE = "0040,0100"; + static const char* SCHEDULED_STATION_AETITLE = "0040,0001"; + static const char* PREGNANCY_STATUS = "0010,21c0"; + + if (!json.isMember(SCHEDULED_PROCEDURE_STEP_SEQUENCE)) + { + // Create a ScheduledProcedureStepSequence sequence, with one empty element + json[SCHEDULED_PROCEDURE_STEP_SEQUENCE] = Json::arrayValue; + json[SCHEDULED_PROCEDURE_STEP_SEQUENCE].append(Json::objectValue); + } + + Json::Value& v = json[SCHEDULED_PROCEDURE_STEP_SEQUENCE]; + + if (v.type() != Json::arrayValue || + v.size() != 1 || + v[0].type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + // Set the ScheduledStationAETitle if none was provided + if (!v[0].isMember(SCHEDULED_STATION_AETITLE) || + v[0].type() != Json::stringValue || + v[0][SCHEDULED_STATION_AETITLE].asString().size() == 0 || + v[0][SCHEDULED_STATION_AETITLE].asString() == "*") + { + v[0][SCHEDULED_STATION_AETITLE] = issuerAet; + } + + if (json.isMember(PREGNANCY_STATUS) && + json[PREGNANCY_STATUS].asString().size() == 0) + { + json.removeMember(PREGNANCY_STATUS); + } + + // Encode the modified JSON as a DICOM instance, then convert it to a C-Find matcher + OrthancPlugins::MemoryBuffer modified(context_); + modified.CreateDicom(json, OrthancPluginCreateDicomFlags_None); + return new OrthancPlugins::FindMatcher(context_, modified); + } +} + + + +OrthancPluginErrorCode Callback(OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* issuerAet, + const char* calledAet) +{ + 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)); + + // Loop over the regular files in the database folder + namespace fs = boost::filesystem; + + fs::path source(folder_); + fs::directory_iterator end; + int parsedFilesCount = 0; + int matchedWorklistCount = 0; + + try + { + for (fs::directory_iterator it(source); it != end; ++it) + { + fs::file_type type(it->status().type()); + + if (type == fs::regular_file || + type == fs::reparse_file) // cf. BitBucket issue #11 + { + std::string extension = fs::extension(it->path()); + std::transform(extension.begin(), extension.end(), extension.begin(), tolower); // Convert to lowercase + + if (extension == ".wl") + { + parsedFilesCount++; + // We found a worklist (i.e. a DICOM find with extension ".wl"), match it against the query + if (MatchWorklist(answers, query, *matcher, it->path().string())) + { + OrthancPlugins::LogInfo(context_, "Worklist matched: " + it->path().string()); + matchedWorklistCount++; + } + } + } + } + + std::ostringstream message; + message << "Worklist C-Find: parsed " << parsedFilesCount << " files, found " << matchedWorklistCount << " match(es)"; + OrthancPlugins::LogInfo(context_, message.str()); + + } + catch (fs::filesystem_error&) + { + OrthancPlugins::LogError(context_, "Inexistent folder while scanning for worklists: " + source.string()); + return OrthancPluginErrorCode_DirectoryExpected; + } + + // Uncomment the following line if too many answers are to be returned + // OrthancPluginMarkWorklistAnswersIncomplete(context_, answers); + + return OrthancPluginErrorCode_Success; + } + catch (OrthancPlugins::PluginException& e) + { + return e.GetErrorCode(); + } +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) + { + context_ = c; + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(c) == 0) + { + OrthancPlugins::ReportMinimalOrthancVersion(context_, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + return -1; + } + + OrthancPlugins::LogWarning(context_, "Sample worklist plugin is initializing"); + OrthancPluginSetDescription(context_, "Serve DICOM modality worklists from a folder with Orthanc."); + + OrthancPlugins::OrthancConfiguration configuration(context_); + + OrthancPlugins::OrthancConfiguration worklists; + configuration.GetSection(worklists, "Worklists"); + + bool enabled = worklists.GetBooleanValue("Enable", false); + if (enabled) + { + if (worklists.LookupStringValue(folder_, "Database")) + { + OrthancPlugins::LogWarning(context_, "The database of worklists will be read from folder: " + folder_); + OrthancPluginRegisterWorklistCallback(context_, Callback); + } + else + { + OrthancPlugins::LogError(context_, "The configuration option \"Worklists.Database\" must contain a path"); + return -1; + } + + filterIssuerAet_ = worklists.GetBooleanValue("FilterIssuerAet", false); + } + else + { + OrthancPlugins::LogWarning(context_, "Worklist server is disabled by the configuration file"); + } + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + OrthancPluginLogWarning(context_, "Sample worklist plugin is finalizing"); + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "worklists"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return MODALITY_WORKLISTS_VERSION; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ModalityWorklists/README Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,11 @@ +Introduction +============ + +This sample plugin enables Orthanc to serve DICOM modality worklists +that are read from some folder. + +The shared library containing the plugin is created as part of the +build process of Orthanc. + +Documentation is available in the Orthanc Book: +http://book.orthanc-server.com/plugins/worklists-plugin.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ModalityWorklists/WorklistsDatabase/Generate.py Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,19 @@ +#!/usr/bin/python + +import os +import subprocess + +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): + ext = os.path.splitext(f) + + if ext[1].lower() == '.dump': + subprocess.check_call([ + 'dump2dcm', + '-g', + '-q', + os.path.join(SOURCE, f), + os.path.join(TARGET, ext[0].lower() + '.wl'), + ])
--- a/Plugins/Samples/ServeFolders/CMakeLists.txt Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/ServeFolders/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -16,10 +16,13 @@ add_library(ServeFolders SHARED Plugin.cpp + ${CMAKE_SOURCE_DIR}/../Common/OrthancPluginCppWrapper.cpp ${JSONCPP_SOURCES} ${BOOST_SOURCES} ) +add_definitions(-DHAS_ORTHANC_EXCEPTION=0) + message("Setting the version of the plugin to ${SERVE_FOLDERS_VERSION}") add_definitions(-DSERVE_FOLDERS_VERSION="${SERVE_FOLDERS_VERSION}")
--- a/Plugins/Samples/ServeFolders/Plugin.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/ServeFolders/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,122 +19,82 @@ **/ -#include <orthanc/OrthancCPlugin.h> +#include "../Common/OrthancPluginCppWrapper.h" #include <json/reader.h> #include <json/value.h> #include <boost/filesystem.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + + +#if HAS_ORTHANC_EXCEPTION == 1 +# error The macro HAS_ORTHANC_EXCEPTION must be set to 0 to compile this plugin +#endif + static OrthancPluginContext* context_ = NULL; +static std::map<std::string, std::string> extensions_; static std::map<std::string, std::string> folders_; static const char* INDEX_URI = "/app/plugin-serve-folders.html"; +static bool allowCache_ = false; +static bool generateETag_ = true; -static const char* GetMimeType(const std::string& path) +static void SetHttpHeaders(OrthancPluginRestOutput* output) { - size_t dot = path.find_last_of('.'); - - std::string extension = (dot == std::string::npos) ? "" : path.substr(dot); - std::transform(extension.begin(), extension.end(), extension.begin(), tolower); - - if (extension == ".html") - { - return "text/html"; - } - else if (extension == ".css") - { - return "text/css"; - } - else if (extension == ".js") - { - return "application/javascript"; - } - else if (extension == ".gif") - { - return "image/gif"; - } - else if (extension == ".svg") + if (!allowCache_) { - return "image/svg+xml"; - } - else if (extension == ".json") - { - return "application/json"; - } - else if (extension == ".xml") - { - return "application/xml"; - } - else if (extension == ".png") - { - return "image/png"; - } - else if (extension == ".jpg" || extension == ".jpeg") - { - return "image/jpeg"; - } - else if (extension == ".woff") - { - return "application/x-font-woff"; - } - else - { - std::string s = "Unknown MIME type for extension: " + extension; - OrthancPluginLogWarning(context_, s.c_str()); - return "application/octet-stream"; + // http://stackoverflow.com/a/2068407/881731 + OrthancPluginSetHttpHeader(context_, output, "Cache-Control", "no-cache, no-store, must-revalidate"); + OrthancPluginSetHttpHeader(context_, output, "Pragma", "no-cache"); + OrthancPluginSetHttpHeader(context_, output, "Expires", "0"); } } -static bool ReadFile(std::string& target, - const std::string& path) +static void RegisterDefaultExtensions() { - OrthancPluginMemoryBuffer buffer; - if (OrthancPluginReadFile(context_, &buffer, path.c_str())) - { - return false; - } - else - { - target.assign(reinterpret_cast<const char*>(buffer.data), buffer.size); - OrthancPluginFreeMemoryBuffer(context_, &buffer); - return true; - } + extensions_["css"] = "text/css"; + extensions_["gif"] = "image/gif"; + extensions_["html"] = "text/html"; + extensions_["jpeg"] = "image/jpeg"; + extensions_["jpg"] = "image/jpeg"; + extensions_["js"] = "application/javascript"; + extensions_["json"] = "application/json"; + extensions_["nexe"] = "application/x-nacl"; + extensions_["nmf"] = "application/json"; + extensions_["pexe"] = "application/x-pnacl"; + extensions_["png"] = "image/png"; + extensions_["svg"] = "image/svg+xml"; + extensions_["wasm"] = "application/wasm"; + extensions_["woff"] = "application/x-font-woff"; + extensions_["xml"] = "application/xml"; } -static bool ReadConfiguration(Json::Value& configuration, - OrthancPluginContext* context) +static std::string GetMimeType(const std::string& path) { - std::string s; + size_t dot = path.find_last_of('.'); + + std::string extension = (dot == std::string::npos) ? "" : path.substr(dot + 1); + std::transform(extension.begin(), extension.end(), extension.begin(), tolower); + std::map<std::string, std::string>::const_iterator found = extensions_.find(extension); + + if (found != extensions_.end() && + !found->second.empty()) { - char* tmp = OrthancPluginGetConfiguration(context); - if (tmp == NULL) - { - OrthancPluginLogError(context, "Error while retrieving the configuration from Orthanc"); - return false; - } - - s.assign(tmp); - OrthancPluginFreeString(context, tmp); - } - - Json::Reader reader; - if (reader.parse(s, configuration)) - { - return true; + return found->second; } else { - OrthancPluginLogError(context, "Unable to parse the configuration"); - return false; + OrthancPlugins::LogWarning(context_, "ServeFolders: Unknown MIME type for extension \"" + extension + "\""); + return "application/octet-stream"; } } - static bool LookupFolder(std::string& folder, OrthancPluginRestOutput* output, const OrthancPluginHttpRequest* request) @@ -143,8 +104,7 @@ std::map<std::string, std::string>::const_iterator found = folders_.find(uri); if (found == folders_.end()) { - std::string s = "Unknown URI in plugin server-folders: " + uri; - OrthancPluginLogError(context_, s.c_str()); + OrthancPlugins::LogError(context_, "Unknown URI in plugin server-folders: " + uri); OrthancPluginSendHttpStatusCode(context_, output, 404); return false; } @@ -156,16 +116,35 @@ } -static OrthancPluginErrorCode FolderCallback(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +static void Answer(OrthancPluginRestOutput* output, + const char* content, + size_t size, + const std::string& mime) +{ + if (generateETag_) + { + OrthancPlugins::OrthancString md5(context_); + md5.Assign(OrthancPluginComputeMd5(context_, content, size)); + + std::string etag = "\"" + std::string(md5.GetContent()) + "\""; + OrthancPluginSetHttpHeader(context_, output, "ETag", etag.c_str()); + } + + SetHttpHeaders(output); + OrthancPluginAnswerBuffer(context_, output, content, size, mime.c_str()); +} + + +void ServeFolder(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { namespace fs = boost::filesystem; if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context_, output, "GET"); - return OrthancPluginErrorCode_Success; + return; } std::string folder; @@ -198,7 +177,10 @@ for (fs::directory_iterator it(parent) ; it != end; ++it) { - if (fs::is_regular_file(it->status())) + fs::file_type type = it->status().type(); + + if (type == fs::regular_file || + type == fs::reparse_file) // cf. BitBucket issue #11 { std::string f = it->path().filename().string(); s += " <li><a href=\"" + f + "\">" + f + "</a></li>\n"; @@ -209,40 +191,42 @@ s += " </body>\n"; s += "</html>\n"; - OrthancPluginAnswerBuffer(context_, output, s.c_str(), s.size(), "text/html"); + Answer(output, s.c_str(), s.size(), "text/html"); } else { std::string path = folder + "/" + item.string(); - const char* mime = GetMimeType(path); + std::string mime = GetMimeType(path); - std::string s; - if (ReadFile(s, path)) + OrthancPlugins::MemoryBuffer content(context_); + + try { - const char* resource = s.size() ? s.c_str() : NULL; - OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime); + content.ReadFile(path); } - else + catch (...) { - std::string s = "Inexistent file in served folder: " + path; - OrthancPluginLogError(context_, s.c_str()); - OrthancPluginSendHttpStatusCode(context_, output, 404); + ORTHANC_PLUGINS_THROW_EXCEPTION(InexistentFile); } + + boost::posix_time::ptime lastModification = boost::posix_time::from_time_t(fs::last_write_time(path)); + std::string t = boost::posix_time::to_iso_string(lastModification); + OrthancPluginSetHttpHeader(context_, output, "Last-Modified", t.c_str()); + + Answer(output, content.GetData(), content.GetSize(), mime); } } - - return OrthancPluginErrorCode_Success; } -static OrthancPluginErrorCode ListServedFolders(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +void ListServedFolders(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context_, output, "GET"); - return OrthancPluginErrorCode_Success; + return; } std::string s = "<html><body><h1>Additional folders served by Orthanc</h1>\n"; @@ -266,9 +250,161 @@ s += "</body></html>\n"; - OrthancPluginAnswerBuffer(context_, output, s.c_str(), s.size(), "text/html"); + Answer(output, s.c_str(), s.size(), "text/html"); +} + + +static void ConfigureFolders(const Json::Value& folders) +{ + if (folders.type() != Json::objectValue) + { + OrthancPlugins::LogError(context_, "The list of folders to be served is badly formatted (must be a JSON object)"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + Json::Value::Members members = folders.getMemberNames(); + + // Register the callback for each base URI + for (Json::Value::Members::const_iterator + it = members.begin(); it != members.end(); ++it) + { + if (folders[*it].type() != Json::stringValue) + { + OrthancPlugins::LogError(context_, "The folder to be served \"" + *it + + "\" must be associated with a string value (its mapped URI)"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + std::string baseUri = *it; + + // Remove the heading and trailing slashes in the root URI, if any + while (!baseUri.empty() && + *baseUri.begin() == '/') + { + baseUri = baseUri.substr(1); + } + + while (!baseUri.empty() && + *baseUri.rbegin() == '/') + { + baseUri.resize(baseUri.size() - 1); + } + + if (baseUri.empty()) + { + OrthancPlugins::LogError(context_, "The URI of a folder to be served cannot be empty"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + // Check whether the source folder exists and is indeed a directory + const std::string folder = folders[*it].asString(); + if (!boost::filesystem::is_directory(folder)) + { + OrthancPlugins::LogError(context_, "Trying and serve an inexistent folder: " + folder); + ORTHANC_PLUGINS_THROW_EXCEPTION(InexistentFile); + } + + folders_[baseUri] = folder; + + // Register the callback to serve the folder + { + const std::string regex = "/(" + baseUri + ")/(.*)"; + OrthancPlugins::RegisterRestCallback<ServeFolder>(context_, regex.c_str(), true); + } + } +} + + +static void ConfigureExtensions(const Json::Value& extensions) +{ + if (extensions.type() != Json::objectValue) + { + OrthancPlugins::LogError(context_, "The list of extensions is badly formatted (must be a JSON object)"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + Json::Value::Members members = extensions.getMemberNames(); - return OrthancPluginErrorCode_Success; + for (Json::Value::Members::const_iterator + it = members.begin(); it != members.end(); ++it) + { + if (extensions[*it].type() != Json::stringValue) + { + OrthancPlugins::LogError(context_, "The file extension \"" + *it + + "\" must be associated with a string value (its MIME type)"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + const std::string& mime = extensions[*it].asString(); + + std::string name = *it; + + if (!name.empty() && + name[0] == '.') + { + name = name.substr(1); // Remove the leading dot "." + } + + extensions_[name] = mime; + + if (mime.empty()) + { + OrthancPlugins::LogWarning(context_, "ServeFolders: Removing MIME type for file extension \"." + name + "\""); + } + else + { + OrthancPlugins::LogWarning(context_, "ServeFolders: Associating file extension \"." + name + + "\" with MIME type \"" + mime + "\""); + } + } +} + + +static void ReadConfiguration() +{ + OrthancPlugins::OrthancConfiguration configuration; + + { + OrthancPlugins::OrthancConfiguration globalConfiguration(context_); + globalConfiguration.GetSection(configuration, "ServeFolders"); + } + + if (!configuration.IsSection("Folders")) + { + // This is a basic configuration + ConfigureFolders(configuration.GetJson()); + } + else + { + // This is an advanced configuration + ConfigureFolders(configuration.GetJson()["Folders"]); + + bool tmp; + + if (configuration.LookupBooleanValue(tmp, "AllowCache")) + { + allowCache_ = tmp; + OrthancPlugins::LogWarning(context_, "ServeFolders: Requesting the HTTP client to " + + std::string(tmp ? "enable" : "disable") + + " its caching mechanism"); + } + + if (configuration.LookupBooleanValue(tmp, "GenerateETag")) + { + generateETag_ = tmp; + OrthancPlugins::LogWarning(context_, "ServeFolders: The computation of an ETag for the served resources is " + + std::string(tmp ? "enabled" : "disabled")); + } + + OrthancPlugins::OrthancConfiguration extensions; + configuration.GetSection(extensions, "Extensions"); + ConfigureExtensions(extensions.GetJson()); + } + + if (folders_.empty()) + { + OrthancPlugins::LogWarning(context_, "ServeFolders: Empty configuration file: No additional folder will be served!"); + } } @@ -281,87 +417,27 @@ /* Check the version of the Orthanc core */ if (OrthancPluginCheckVersion(context_) == 0) { - char info[1024]; - sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", - context_->orthancVersion, - ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); - OrthancPluginLogError(context_, info); - return -1; - } - - OrthancPluginSetDescription(context_, "Serve additional folders with the HTTP server of Orthanc."); - - Json::Value configuration; - if (!ReadConfiguration(configuration, context_)) - { + OrthancPlugins::ReportMinimalOrthancVersion(context_, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); return -1; } - if (configuration.isMember("ServeFolders")) - { - if (configuration["ServeFolders"].type() != Json::objectValue) - { - OrthancPluginLogError(context_, "The \"ServeFolders\" configuration section is badly formatted (must be a JSON object)"); - return -1; - } - } - else - { - OrthancPluginLogWarning(context_, "No section \"ServeFolders\" in your configuration file: " - "No additional folder will be served!"); - configuration["ServeFolders"] = Json::objectValue; - } - - - Json::Value::Members members = configuration["ServeFolders"].getMemberNames(); - - // Register the callback for each base URI - for (Json::Value::Members::const_iterator - it = members.begin(); it != members.end(); ++it) - { - std::string baseUri = *it; - - // Remove the heading and trailing slashes in the root URI, if any - while (!baseUri.empty() && - *baseUri.begin() == '/') - { - baseUri = baseUri.substr(1); - } + RegisterDefaultExtensions(); + OrthancPluginSetDescription(context_, "Serve additional folders with the HTTP server of Orthanc."); + OrthancPluginSetRootUri(context, INDEX_URI); + OrthancPlugins::RegisterRestCallback<ListServedFolders>(context_, INDEX_URI, true); - while (!baseUri.empty() && - *baseUri.rbegin() == '/') - { - baseUri.resize(baseUri.size() - 1); - } - - if (baseUri.empty()) - { - OrthancPluginLogError(context_, "The URI of a folder to be served cannot be empty"); - return -1; - } - - // Check whether the source folder exists and is indeed a directory - const std::string folder = configuration["ServeFolders"][*it].asString(); - if (!boost::filesystem::is_directory(folder)) - { - std::string msg = "Trying and serve an inexistent folder: " + folder; - OrthancPluginLogError(context_, msg.c_str()); - return -1; - } - - folders_[baseUri] = folder; - - // Register the callback to serve the folder - { - const std::string regex = "/(" + baseUri + ")/(.*)"; - OrthancPluginRegisterRestCallback(context, regex.c_str(), FolderCallback); - } + try + { + ReadConfiguration(); } - - OrthancPluginRegisterRestCallback(context, INDEX_URI, ListServedFolders); - OrthancPluginSetRootUri(context, INDEX_URI); + catch (OrthancPlugins::PluginException& e) + { + OrthancPlugins::LogError(context_, "Error while initializing the ServeFolders plugin: " + + std::string(e.What(context_))); + } return 0; }
--- a/Plugins/Samples/ServeFolders/README Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/ServeFolders/README Fri Oct 12 15:18:10 2018 +0200 @@ -1,52 +1,11 @@ -Introduction -============ +ServeFolders plugin +=================== This sample plugin enables Orthanc to serve additional folders using its embedded Web server. - -Compilation for Linux -===================== - -# mkdir Build -# cd Build -# cmake .. -# make - - -Cross-compilation for Windows using MinGW -========================================= - -# mkdir Build -# cd Build -# cmake .. -DCMAKE_TOOLCHAIN_FILE=../../../Resources/MinGWToolchain.cmake -# make - - -Configuration -============= +The shared library containing the plugin is created as part of the +build process of Orthanc. -First, generate the default configuration of Orthanc: -https://code.google.com/p/orthanc/wiki/OrthancConfiguration - -Then, modify the "Plugins" option to point to the folder containing -the built plugins. - -Finally, create a section "ServeFolders" in the configuration file to -specify which folder you want to serve, and at which URI. For -instance, the following excerpt would load the plugins from the -working directory, then would branch the content of the folder -"/home/jodogne/WWW/fosdem" as the URI "http://localhost:8042/fosdem": - -{ - "Name" : "MyOrthanc", - [...] - "HttpPort" : 8042, - [...] - "Plugins" : [ - "." - ], - "ServeFolders" : { - "/fosdem" : "/home/jodogne/WWW/fosdem" - } -} +Documentation is available in the Orthanc Book: +http://book.orthanc-server.com/plugins/serve-folders.html
--- a/Plugins/Samples/StorageArea/Plugin.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/StorageArea/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/Configuration.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Configuration.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,6 +1,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # 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/Framework.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/Framework.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,6 +1,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,6 +66,10 @@ { return "application/xml"; } + else if (extension == ".wasm") + { + return "application/wasm"; + } else if (extension == ".png") { return "image/png";
--- a/README Thu Oct 29 11:25:45 2015 +0100 +++ b/README Fri Oct 12 15:18:10 2018 +0200 @@ -16,11 +16,14 @@ Supported Platforms ------------------- -Currently, the supported platforms are: +Currently, the officially validated platforms are: -* Linux 32bit. -* Linux 64bit. -* Windows 32bit. +* GNU/Linux (32bit and 64bit). +* Windows (32bit and 64bit). +* Apple OS X (32bit and 64bit). + +Orthanc is known to work on other UNIX-like platforms (such as FreeBSD +and OpenBSD). Supported Toolchains @@ -29,9 +32,9 @@ Orthanc can currently be built using the following compiling toolchains: -* Native Linux compilation, with gcc. +* Native GNU/Linux compilation, with gcc. * Native Windows compilation, with Microsoft Visual Studio. -* Cross-compilation for Windows under Linux, with MinGW. +* Cross-compilation for Windows under GNU/Linux, with MinGW. Licensing @@ -47,16 +50,19 @@ use of Orthanc to warn us about this use. You can cite our work using the following BibTeX entry: -@inproceedings{Jodogne:ISBI2013, - author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.}, - title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research}, - booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, - year={2013}, - pages={190-193}, - ISSN={1945-7928}, - month=apr, - url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444}, - address={San Francisco, {CA}, {USA}} +@Article{Jodogne2018, + author="Jodogne, S{\'e}bastien", + title="The {O}rthanc Ecosystem for Medical Imaging", + journal="Journal of Digital Imaging", + year="2018", + month="Jun", + day="01", + volume="31", + number="3", + pages="341--352", + issn="1618-727X", + doi="10.1007/s10278-018-0082-y", + url="https://doi.org/10.1007/s10278-018-0082-y" }
--- a/Resources/CMake/BoostConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/BoostConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -7,10 +7,26 @@ #set(Boost_DEBUG 1) #set(Boost_USE_STATIC_LIBS ON) - find_package(Boost - COMPONENTS filesystem thread system date_time regex locale) + if (ENABLE_LOCALE) + list(APPEND ORTHANC_BOOST_COMPONENTS locale) + endif() + + list(APPEND ORTHANC_BOOST_COMPONENTS filesystem thread system date_time regex) + find_package(Boost COMPONENTS "${ORTHANC_BOOST_COMPONENTS}") if (NOT Boost_FOUND) + foreach (item ${ORTHANC_BOOST_COMPONENTS}) + string(TOUPPER ${item} tmp) + + if (Boost_${tmp}_FOUND) + set(tmp2 "found") + else() + set(tmp2 "missing") + endif() + + message("Boost component ${item} - ${tmp2}") + endforeach() + message(FATAL_ERROR "Unable to locate Boost on this system") endif() @@ -27,86 +43,58 @@ ) endif() - #if (${Boost_VERSION} LESS 104800) - # boost::locale is only available from 1.48.00 - #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version") - # set(BOOST_STATIC 1) - #endif() - include_directories(${Boost_INCLUDE_DIRS}) link_libraries(${Boost_LIBRARIES}) endif() if (BOOST_STATIC) - # Parameters for Boost 1.58.0 - set(BOOST_NAME boost_1_58_0) - set(BOOST_BCP_SUFFIX bcpdigest-0.9.2) - set(BOOST_MD5 "704b110917cbda903e07cb53934b47ac") - set(BOOST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz") - set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") + ## + ## Parameters for static compilation of Boost + ## + + set(BOOST_NAME boost_1_67_0) + set(BOOST_VERSION 1.67.0) + set(BOOST_BCP_SUFFIX bcpdigest-1.4.0) + set(BOOST_MD5 "fb3535a88e72c3d4c4d06b047b8e57fe") + set(BOOST_URL "http://www.orthanc-server.com/downloads/third-party/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz") set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) + if (IS_DIRECTORY "${BOOST_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) + endif() + DownloadPackage(${BOOST_MD5} ${BOOST_URL} "${BOOST_SOURCES_DIR}") - set(BOOST_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") - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp - ) - add_definitions( - -DBOOST_LOCALE_WITH_ICONV=1 - ) - - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - add_definitions(-DBOOST_HAS_SCHED_YIELD=1) - endif() + ## + ## Patching boost + ## - elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp - ) + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/boost-${BOOST_VERSION}-linux-standard-base.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) - # Starting with release 0.8.2, Orthanc statically links against - # libiconv, even on Windows. Indeed, the "WCONV" library of - # Windows XP seems not to support properly several codepages - # (notably "Latin3", "Hebrew", and "Arabic"). - - if (USE_BOOST_ICONV) - include(${ORTHANC_ROOT}/Resources/CMake/LibIconvConfiguration.cmake) - else() - add_definitions(-DBOOST_LOCALE_WITH_WCONV=1) - endif() - - else() - message(FATAL_ERROR "Support your platform here") + if (FirstRun AND Failure) + message(FATAL_ERROR "Error while patching a file") endif() - if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp - ) + + ## + ## Generic configuration of Boost + ## + + if (CMAKE_COMPILER_IS_GNUCXX) + add_definitions(-isystem ${BOOST_SOURCES_DIR}) endif() - aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) - - list(APPEND BOOST_SOURCES - ${BOOST_REGEX_SOURCES} - ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp - ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp - ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp + include_directories( + ${BOOST_SOURCES_DIR} ) add_definitions( @@ -119,27 +107,213 @@ -DBOOST_REGEX_NO_LIB -DBOOST_SYSTEM_NO_LIB -DBOOST_LOCALE_NO_LIB - -DBOOST_HAS_LOCALE=1 - -DBOOST_HAS_FILESYSTEM_V3=1 + ) + + set(BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp ) - if (CMAKE_COMPILER_IS_GNUCXX) - add_definitions(-isystem ${BOOST_SOURCES_DIR}) + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase" OR + "${CMAKE_SYSTEM_NAME}" STREQUAL "Android") + add_definitions( + -DBOOST_SYSTEM_USE_STRERROR=1 + ) endif() - include_directories( - ${BOOST_SOURCES_DIR} + + ## + ## Configuration of boost::thread + ## + + 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 "PNaCl" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl64" OR + CMAKE_SYSTEM_NAME STREQUAL "Android") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/atomic/src/lockpool.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp + ) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase" OR + CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl64") + add_definitions(-DBOOST_HAS_SCHED_YIELD=1) + endif() + + elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp + ) + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + # No support for threads in asm.js/WebAssembly + + else() + message(FATAL_ERROR "Support your platform here") + endif() + + + ## + ## Configuration of boost::regex + ## + + aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) + + list(APPEND BOOST_SOURCES + ${BOOST_REGEX_SOURCES} + ) + + + ## + ## Configuration of boost::datetime + ## + + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp ) - source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) -else() - add_definitions( - -DBOOST_HAS_LOCALE=1 - ) -endif() + + ## + ## Configuration of boost::filesystem + ## + + if (CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl64" OR + CMAKE_SYSTEM_NAME STREQUAL "Android") + # boost::filesystem is not available on PNaCl + add_definitions( + -DBOOST_HAS_FILESYSTEM_V3=0 + -D__INTEGRITY=1 + ) + else() + add_definitions( + -DBOOST_HAS_FILESYSTEM_V3=1 + ) + list(APPEND BOOST_SOURCES + ${BOOST_NAME}/libs/filesystem/src/codecvt_error_category.cpp + ${BOOST_NAME}/libs/filesystem/src/operations.cpp + ${BOOST_NAME}/libs/filesystem/src/path.cpp + ${BOOST_NAME}/libs/filesystem/src/path_traits.cpp + ) + + if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp + ) + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND BOOST_SOURCES + ${BOOST_NAME}/libs/filesystem/src/windows_file_codecvt.cpp + ) + endif() + endif() -add_definitions( - -DBOOST_HAS_DATE_TIME=1 - -DBOOST_HAS_REGEX=1 - ) + ## + ## Configuration of boost::locale + ## + + if (NOT ENABLE_LOCALE) + message("boost::locale is disabled") + else() + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/shared/generator.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/shared/date_time.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/shared/formatting.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/shared/ids.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/shared/localization_backend.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/shared/message.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/shared/mo_lambda.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/util/codecvt_converter.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/util/default_locale.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/util/gregorian.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/util/info.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/util/locale_data.cpp + ) + + if (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR + CMAKE_SYSTEM_VERSION STREQUAL "LinuxStandardBase") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/locale/src/std/codecvt.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/std/collate.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/std/converter.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/std/numeric.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/std/std_backend.cpp + ) + + add_definitions( + -DBOOST_LOCALE_WITH_ICONV=1 + -DBOOST_LOCALE_NO_WINAPI_BACKEND=1 + -DBOOST_LOCALE_NO_POSIX_BACKEND=1 + ) + + elseif (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 "PNaCl" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl64" OR + CMAKE_SYSTEM_NAME STREQUAL "Emscripten") # For WebAssembly or asm.js + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/locale/src/posix/codecvt.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/posix/converter.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/posix/numeric.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/posix/posix_backend.cpp + ) + + add_definitions( + -DBOOST_LOCALE_WITH_ICONV=1 + -DBOOST_LOCALE_NO_WINAPI_BACKEND=1 + -DBOOST_LOCALE_NO_STD_BACKEND=1 + ) + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/locale/src/win32/collate.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/win32/converter.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/win32/lcid.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/win32/numeric.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/win32/win_backend.cpp + ) + + add_definitions( + -DBOOST_LOCALE_NO_POSIX_BACKEND=1 + -DBOOST_LOCALE_NO_STD_BACKEND=1 + ) + + # Starting with release 0.8.2, Orthanc statically links against + # libiconv, even on Windows. Indeed, the "WCONV" library of + # Windows XP seems not to support properly several codepages + # (notably "Latin3", "Hebrew", and "Arabic"). Set + # "USE_BOOST_ICONV" to "OFF" to use WCONV anyway. + + if (USE_BOOST_ICONV) + add_definitions(-DBOOST_LOCALE_WITH_ICONV=1) + else() + add_definitions(-DBOOST_LOCALE_WITH_WCONV=1) + endif() + + else() + message(FATAL_ERROR "Support your platform here") + endif() + endif() + + + source_group(ThirdParty\\boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) + +endif()
--- a/Resources/CMake/BoostConfiguration.sh Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/BoostConfiguration.sh Fri Oct 12 15:18:10 2018 +0200 @@ -13,22 +13,53 @@ ## History: ## - Orthanc between 0.6.2 and 0.7.3: Boost 1.54.0 ## - Orthanc between 0.7.4 and 0.9.1: Boost 1.55.0 -## - Orthanc >= 0.9.2: Boost 1.58.0 +## - Orthanc between 0.9.2 and 0.9.4: Boost 1.58.0 +## - Orthanc between 0.9.5 and 1.0.0: Boost 1.59.0 +## - Orthanc between 1.1.0 and 1.2.0: Boost 1.60.0 +## - Orthanc 1.3.0: Boost 1.64.0 +## - Orthanc 1.3.1: Boost 1.65.1 +## - Orthanc 1.3.2: Boost 1.66.0 +## - Orthanc >= 1.4.0: Boost 1.67.0 -rm -rf /tmp/boost_1_58_0 -rm -rf /tmp/bcp/boost_1_58_0 +BOOST_VERSION=1_67_0 +ORTHANC_VERSION=1.4.0 + +rm -rf /tmp/boost_${BOOST_VERSION} +rm -rf /tmp/bcp/boost_${BOOST_VERSION} cd /tmp -echo "Uncompressing the sources of Boost 1.58.0..." -tar xfz ./boost_1_58_0.tar.gz +echo "Uncompressing the sources of Boost ${BOOST_VERSION}..." +tar xfz ./boost_${BOOST_VERSION}.tar.gz echo "Generating the subset..." -mkdir -p /tmp/bcp/boost_1_58_0 -bcp --boost=/tmp/boost_1_58_0 thread system locale date_time filesystem math/special_functions algorithm uuid atomic /tmp/bcp/boost_1_58_0 -cd /tmp/bcp +mkdir -p /tmp/bcp/boost_${BOOST_VERSION} +bcp --boost=/tmp/boost_${BOOST_VERSION} thread system locale date_time filesystem math/special_functions algorithm uuid atomic iostreams program_options numeric/ublas geometry polygon signals2 /tmp/bcp/boost_${BOOST_VERSION} + +echo "Removing documentation..." +rm -rf /tmp/bcp/boost_${BOOST_VERSION}/libs/locale/doc/html +rm -rf /tmp/bcp/boost_${BOOST_VERSION}/libs/algorithm/doc/html +rm -rf /tmp/bcp/boost_${BOOST_VERSION}/libs/geometry/doc/html +rm -rf /tmp/bcp/boost_${BOOST_VERSION}/libs/geometry/doc/doxy/doxygen_output/html +rm -rf /tmp/bcp/boost_${BOOST_VERSION}/libs/filesystem/example/ + +# https://stackoverflow.com/questions/1655372/longest-line-in-a-file +LONGEST_FILENAME=`find /tmp/bcp/ | awk '{print length, $0}' | sort -nr | head -1` +LONGEST=`echo "$LONGEST_FILENAME" | cut -d ' ' -f 1` + +echo +echo "Longest filename (${LONGEST} characters):" +echo "${LONGEST_FILENAME}" +echo + +if [ ${LONGEST} -ge 128 ]; then + echo "ERROR: Too long filename for Windows!" + echo + exit -1 +fi echo "Compressing the subset..." -tar cfz boost_1_58_0_bcpdigest-0.9.2.tar.gz boost_1_58_0 -ls -l boost_1_58_0_bcpdigest-0.9.2.tar.gz -md5sum boost_1_58_0_bcpdigest-0.9.2.tar.gz -readlink -f boost_1_58_0_bcpdigest-0.9.2.tar.gz +cd /tmp/bcp +tar cfz boost_${BOOST_VERSION}_bcpdigest-${ORTHANC_VERSION}.tar.gz boost_${BOOST_VERSION} +ls -l boost_${BOOST_VERSION}_bcpdigest-${ORTHANC_VERSION}.tar.gz +md5sum boost_${BOOST_VERSION}_bcpdigest-${ORTHANC_VERSION}.tar.gz +readlink -f boost_${BOOST_VERSION}_bcpdigest-${ORTHANC_VERSION}.tar.gz
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/CivetwebConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,55 @@ +if (STATIC_BUILD OR NOT USE_SYSTEM_CIVETWEB) + set(CIVETWEB_SOURCES_DIR ${CMAKE_BINARY_DIR}/civetweb-1.9.1) + set(CIVETWEB_URL "http://www.orthanc-server.com/downloads/third-party/civetweb-1.9.1.tar.gz") + set(CIVETWEB_MD5 "c713f7336582d1a78897971260c67c2a") + + DownloadPackage(${CIVETWEB_MD5} ${CIVETWEB_URL} "${CIVETWEB_SOURCES_DIR}") + + include_directories( + ${CIVETWEB_SOURCES_DIR}/include + ) + + set(CIVETWEB_SOURCES + ${CIVETWEB_SOURCES_DIR}/src/civetweb.c + ) + + + if (ENABLE_SSL) + add_definitions( + -DNO_SSL_DL=1 + ) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") + link_libraries(dl) + endif() + + else() + add_definitions( + -DNO_SSL=1 # Remove SSL support from civetweb + ) + endif() + + + if (CMAKE_SYSTEM_NAME STREQUAL "Windows" AND + CMAKE_COMPILER_IS_GNUCXX) + # This is a patch for MinGW64 + add_definitions(-D_TIMESPEC_DEFINED=1) + endif() + + source_group(ThirdParty\\Civetweb REGULAR_EXPRESSION ${CIVETWEB_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(civetweb.h HAVE_CIVETWEB_H) + if (NOT HAVE_CIVETWEB_H) + message(FATAL_ERROR "Please install the libcivetweb-devel package") + endif() + + cmake_reset_check_state() + set(CMAKE_REQUIRED_LIBRARIES dl pthread) + CHECK_LIBRARY_EXISTS(civetweb mg_start "" HAVE_CIVETWEB_LIB) + if (NOT HAVE_CIVETWEB_LIB) + message(FATAL_ERROR "Please install the libcivetweb-devel package") + endif() + + link_libraries(civetweb) +endif()
--- a/Resources/CMake/Compiler.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/Compiler.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,16 +1,18 @@ # This file sets all the compiler-related flags -if (CMAKE_CROSSCOMPILING) +if (CMAKE_CROSSCOMPILING OR + "${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") # Cross-compilation necessarily implies standalone and static build SET(STATIC_BUILD ON) SET(STANDALONE_BUILD ON) endif() if (CMAKE_COMPILER_IS_GNUCXX) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long") + # --std=c99 makes libcurl not to compile # -pedantic gives a lot of warnings on OpenSSL - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wno-long-long -Wno-variadic-macros") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wno-variadic-macros") if (CMAKE_CROSSCOMPILING) # http://stackoverflow.com/a/3543845/881731 @@ -41,31 +43,80 @@ -D_CRT_SECURE_NO_WARNINGS=1 -D_CRT_SECURE_NO_DEPRECATE=1 ) - include_directories(${ORTHANC_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(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio) + endif() + link_libraries(netapi32) endif() +if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + # In FreeBSD/OpenBSD, the "/usr/local/" folder contains the ports and need to be imported + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/usr/local/include") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib") +endif() + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map") + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + # The "--no-undefined" linker flag makes the shared libraries + # (plugins ModalityWorklists and ServeFolders) fail to compile on OpenBSD + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") + endif() + + if (NOT DEFINED ENABLE_PLUGINS_VERSION_SCRIPT OR + ENABLE_PLUGINS_VERSION_SCRIPT) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map") + endif() # Remove the "-rdynamic" option # http://www.mail-archive.com/cmake@cmake.org/msg08837.html set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") - link_libraries(uuid pthread rt) + link_libraries(pthread) + + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + link_libraries(rt) + endif() - if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND + NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + link_libraries(dl) + endif() + + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND + NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + # The "--as-needed" linker flag is not available on FreeBSD and OpenBSD set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed") + endif() + + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND + NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + # FreeBSD/OpenBSD have just one single interface for file + # handling, which is 64bit clean, so there is no need to define macro + # for LFS (Large File Support). + # https://ohse.de/uwe/articles/lfs.html add_definitions( -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64 ) - link_libraries(dl) endif() elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") @@ -92,6 +143,11 @@ link_libraries(rpcrt4 ws2_32) if (CMAKE_COMPILER_IS_GNUCXX) + # Some additional C/C++ compiler flags for MinGW + SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable") + 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_SHARED_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++") @@ -115,33 +171,48 @@ ) link_libraries(iconv) -endif() - +elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + message("Building using Emscripten (for WebAssembly or asm.js targets)") -if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}") + # The BINARYEN_TRAP_MODE specifies what to do when divisions per + # 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. + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]' -s BINARYEN_TRAP_MODE='\"clamp\"'") + +elseif (CMAKE_SYSTEM_NAME STREQUAL "Android") + +else() + message("Unknown target platform: ${CMAKE_SYSTEM_NAME}") + message(FATAL_ERROR "Support your platform here") endif() -if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") - # In FreeBSD, the "/usr/local/" folder contains the ports and need to be imported - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include") - SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib") +if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING) + if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -pg") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg") + else() + message(FATAL_ERROR "Don't know how to enable profiling on your configuration") + endif() endif() -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - CHECK_INCLUDE_FILES(rpc.h HAVE_UUID_H) -else() - CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H) -endif() - -if (NOT HAVE_UUID_H) - message(FATAL_ERROR "Please install the uuid-dev package") +if (CMAKE_COMPILER_IS_GNUCXX) + # "When creating a static library using binutils (ar) and there + # exist a duplicate object name (e.g. a/Foo.cpp.o, b/Foo.cpp.o), the + # resulting static library can end up having only one of the + # duplicate objects. [...] This bug only happens if there are many + # objects." The trick consists in replacing the "r" argument + # ("replace") provided to "ar" (as used in CMake < 3.1) by the "q" + # argument ("quick append"). This is because of the fact that CMake + # will invoke "ar" several times with several batches of ".o" + # objects, and using "r" would overwrite symbols defined in + # preceding batches. https://cmake.org/Bug/view.php?id=14874 + set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> <LINK_FLAGS> q <TARGET> <OBJECTS>") endif()
--- a/Resources/CMake/DcmtkConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/DcmtkConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,25 +1,34 @@ -# Lookup for DICOM dictionaries, if none is specified by the user -if (DCMTK_DICTIONARY_DIR STREQUAL "") - find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic - /usr/share/dcmtk - /usr/share/libdcmtk2 - /usr/local/share/dcmtk - ) - - message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}") - add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}") -else() - add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") +if (NOT DEFINED ENABLE_DCMTK_NETWORKING) + set(ENABLE_DCMTK_NETWORKING ON) endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) + if (USE_DCMTK_360) + SET(DCMTK_VERSION_NUMBER 360) + SET(DCMTK_PACKAGE_VERSION "3.6.0") + SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) + SET(DCMTK_URL "http://www.orthanc-server.com/downloads/third-party/dcmtk-3.6.0.zip") + SET(DCMTK_MD5 "219ad631b82031806147e4abbfba4fa4") + else() + SET(DCMTK_VERSION_NUMBER 362) + SET(DCMTK_PACKAGE_VERSION "3.6.2") + SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.2) + SET(DCMTK_URL "http://www.orthanc-server.com/downloads/third-party/dcmtk-3.6.2.tar.gz") + SET(DCMTK_MD5 "d219a4152772985191c9b89d75302d12") -if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) - SET(DCMTK_VERSION_NUMBER 360) - set(DCMTK_PACKAGE_VERSION "3.6.0") - SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - SET(DCMTK_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip") - SET(DCMTK_MD5 "219ad631b82031806147e4abbfba4fa4") + macro(DCMTK_UNSET) + endmacro() + + macro(DCMTK_UNSET_CACHE) + endmacro() + set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/) + set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/) + set(DCMTK_WITH_THREADS ON) + + add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1) + endif() + if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}") set(FirstRun OFF) else() @@ -28,26 +37,141 @@ DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}") + + if (FirstRun) + if (USE_DCMTK_360) + # If using DCMTK 3.6.0, backport the "private.dic" file from DCMTK + # 3.6.2. This adds support for more private tags, and fixes some + # import problems with Philips MRI Achieva. + if (USE_DCMTK_362_PRIVATE_DIC) + message("Using the dictionary of private tags from DCMTK 3.6.2") + configure_file( + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2-private.dic + ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic + COPYONLY) + else() + message("Using the dictionary of private tags from DCMTK 3.6.0") + endif() + + # Patches specific to DCMTK 3.6.0 + message("Applying patch to solve vulnerability in DCMTK 3.6.0") + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.0-dulparse-vulnerability.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() + + # This patch is not needed anymore thanks to the following commit + # (information sent by Jorg Riesmeier on Twitter on 2017-07-19): + # http://git.dcmtk.org/?p=dcmtk.git;a=commit;h=8df1f5e517b8629ae09088d0935c2a8dd333c76f + message("Applying patch for speed in DCMTK 3.6.0") + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.0-speed.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() + + else() + # "3.6.2 CXX11 fails on Linux; patch suggestions included" + # https://forum.dcmtk.org/viewtopic.php?f=3&t=4637 + 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 + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() + endif() + else() + message("The patches for DCMTK have already been applied") + endif() + + + # C_CHAR_UNSIGNED *must* be set before calling "GenerateDCMTKConfigure.cmake" IF (CMAKE_CROSSCOMPILING) - SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") + 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 "") + + 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") + endif() + 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_VERSION}" STREQUAL "LinuxStandardBase") - set(HAVE_SSTREAM 1) - set(HAVE_PROTOTYPE_BZERO 1) - set(HAVE_PROTOTYPE_GETHOSTNAME 1) - set(HAVE_PROTOTYPE_GETSOCKOPT 1) - set(HAVE_PROTOTYPE_SETSOCKOPT 1) - set(HAVE_PROTOTYPE_CONNECT 1) - set(HAVE_PROTOTYPE_BIND 1) - set(HAVE_PROTOTYPE_ACCEPT 1) - set(HAVE_PROTOTYPE_SETSOCKNAME 1) - set(HAVE_PROTOTYPE_GETSOCKNAME 1) + + 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}) @@ -55,12 +179,37 @@ ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) + if (NOT USE_DCMTK_360) + 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() + endif() + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) + if (ENABLE_DCMTK_NETWORKING) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmnet/include + ) + endif() - if (ENABLE_JPEG) + if (ENABLE_DCMTK_JPEG) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12 DCMTK_SOURCES) @@ -74,11 +223,21 @@ ) list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/ddpiimpl.cc + + # 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 + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc ) endif() - if (ENABLE_JPEG_LOSSLESS) + if (ENABLE_DCMTK_JPEG_LOSSLESS) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libsrc DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libcharls DCMTK_SOURCES) include_directories( @@ -88,6 +247,9 @@ ) list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc + + # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1) + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc ) list(APPEND DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc @@ -100,31 +262,27 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") + ${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 ) - - execute_process( - COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${ORTHANC_ROOT}/Resources/Patches/dcmtk-linux-speed.patch - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - - if (Failure AND FirstRun) - message(FATAL_ERROR "Error while patching a file") - endif() 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 ) - if (CMAKE_COMPILER_IS_GNUCXX) + if (CMAKE_COMPILER_IS_GNUCXX AND + USE_DCMTK_360) # This is a patch for MinGW64 execute_process( - COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${ORTHANC_ROOT}/Resources/Patches/dcmtk-mingw64.patch + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.0-mingw64.patch WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) @@ -133,24 +291,26 @@ message(FATAL_ERROR "Error while patching a file") endif() endif() - - # This patch improves speed, even for Windows - execute_process( - COMMAND ${PATCH_EXECUTABLE} -p0 -N - INPUT_FILE ${ORTHANC_ROOT}/Resources/Patches/dcmtk-linux-speed.patch - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - - if (Failure AND FirstRun) - message(FATAL_ERROR "Error while patching a file") - endif() endif() + + if (NOT USE_DCMTK_360 AND + 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 - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc ) #set_source_files_properties(${DCMTK_SOURCES} @@ -167,7 +327,6 @@ include_directories( #${DCMTK_SOURCES_DIR} ${DCMTK_SOURCES_DIR}/config/include - ${DCMTK_SOURCES_DIR}/dcmnet/include ${DCMTK_SOURCES_DIR}/ofstd/include ${DCMTK_SOURCES_DIR}/oflog/include ${DCMTK_SOURCES_DIR}/dcmdata/include @@ -178,17 +337,16 @@ set(DCMTK_BUNDLES_LOG4CPLUS 1) if (STANDALONE_BUILD) - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1) + set(DCMTK_USE_EMBEDDED_DICTIONARIES 1) + set(DCMTK_DICTIONARIES + DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic + DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic + DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic + ) else() - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + set(DCMTK_USE_EMBEDDED_DICTIONARIES 0) endif() - set(DCMTK_DICTIONARIES - DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic - DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic - DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic - ) - else() # The following line allows to manually add libraries at the # command-line, which is necessary for Ubuntu/Debian packages @@ -196,20 +354,24 @@ include(FindDCMTK) list(APPEND DCMTK_LIBRARIES "${tmp}") - include_directories(${DCMTK_INCLUDE_DIR}) + include_directories(${DCMTK_INCLUDE_DIRS}) add_definitions( -DHAVE_CONFIG_H=1 ) - if (EXISTS "${DCMTK_DIR}/config/cfunix.h") - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h") - elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h") # This is for Arch Linux - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h") + if (EXISTS "${DCMTK_config_INCLUDE_DIR}/cfunix.h") + set(DCMTK_CONFIGURATION_FILE "${DCMTK_config_INCLUDE_DIR}/cfunix.h") + elseif (EXISTS "${DCMTK_config_INCLUDE_DIR}/osconfig.h") # This is for Arch Linux + set(DCMTK_CONFIGURATION_FILE "${DCMTK_config_INCLUDE_DIR}/osconfig.h") + elseif (EXISTS "${DCMTK_INCLUDE_DIRS}/dcmtk/config/osconfig.h") # This is for Debian Buster + set(DCMTK_CONFIGURATION_FILE "${DCMTK_INCLUDE_DIRS}/dcmtk/config/osconfig.h") else() - message(FATAL_ERROR "Please install libdcmtk1-dev") + message(FATAL_ERROR "Please install libdcmtk*-dev") endif() + message("DCMTK configuration file: ${DCMTK_CONFIGURATION_FILE}") + # Autodetection of the version of DCMTK file(STRINGS "${DCMTK_CONFIGURATION_FILE}" @@ -222,9 +384,50 @@ DCMTK_VERSION_NUMBER ${DCMTK_VERSION_NUMBER1}) - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + set(DCMTK_USE_EMBEDDED_DICTIONARIES 0) +endif() -endif() add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}) message("DCMTK version: ${DCMTK_VERSION_NUMBER}") + + +add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=${DCMTK_USE_EMBEDDED_DICTIONARIES}) +if (NOT DCMTK_USE_EMBEDDED_DICTIONARIES) + # Lookup for DICOM dictionaries, if none is specified by the user + if (DCMTK_DICTIONARY_DIR STREQUAL "") + find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic + /usr/share/dcmtk + /usr/share/libdcmtk1 + /usr/share/libdcmtk2 + /usr/share/libdcmtk3 + /usr/share/libdcmtk4 + /usr/share/libdcmtk5 + /usr/share/libdcmtk6 + /usr/share/libdcmtk7 + /usr/share/libdcmtk8 + /usr/share/libdcmtk9 + /usr/share/libdcmtk10 + /usr/share/libdcmtk11 + /usr/share/libdcmtk12 + /usr/share/libdcmtk13 + /usr/share/libdcmtk14 + /usr/share/libdcmtk15 + /usr/share/libdcmtk16 + /usr/share/libdcmtk17 + /usr/share/libdcmtk18 + /usr/share/libdcmtk19 + /usr/share/libdcmtk20 + /usr/local/share/dcmtk + ) + + if (${DCMTK_DICTIONARY_DIR_AUTO} MATCHES "DCMTK_DICTIONARY_DIR_AUTO-NOTFOUND") + message(FATAL_ERROR "Cannot locate the DICOM dictionary on this system") + endif() + + message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}") + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}") + else() + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") + endif() +endif()
--- a/Resources/CMake/DownloadPackage.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/DownloadPackage.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -15,12 +15,18 @@ ## Setup the patch command-line tool ## -if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") - set(PATCH_EXECUTABLE ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/patch/patch.exe) -else () - find_program(PATCH_EXECUTABLE patch) - if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND") - message(FATAL_ERROR "Please install the 'patch' standard command-line tool") +if (NOT ORTHANC_DISABLE_PATCH) + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe) + if (NOT EXISTS ${PATCH_EXECUTABLE}) + message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc") + endif() + + else () + find_program(PATCH_EXECUTABLE patch) + if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'patch' standard command-line tool") + endif() endif() endif() @@ -70,9 +76,24 @@ message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") endif() - file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}") + if ("${MD5}" STREQUAL "no-check") + message(WARNING "Not checking the MD5 of: ${Url}") + file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS TIMEOUT 60 INACTIVITY_TIMEOUT 60) + else() + file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS TIMEOUT 60 INACTIVITY_TIMEOUT 60 EXPECTED_MD5 "${MD5}") + endif() + else() message("Using local copy of ${Url}") + + if ("${MD5}" STREQUAL "no-check") + message(WARNING "Not checking the MD5 of: ${Url}") + else() + file(MD5 ${TMP_PATH} ActualMD5) + if (NOT "${ActualMD5}" STREQUAL "${MD5}") + message(FATAL_ERROR "The MD5 hash of a previously download file is invalid: ${TMP_PATH}") + endif() + endif() endif() GetUrlExtension(TMP_EXTENSION "${Url}") @@ -83,7 +104,9 @@ # How to silently extract files using 7-zip # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly - if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + if (("${TMP_EXTENSION}" STREQUAL "gz") OR + ("${TMP_EXTENSION}" STREQUAL "tgz") OR + ("${TMP_EXTENSION}" STREQUAL "xz")) execute_process( COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} @@ -97,8 +120,10 @@ if ("${TMP_EXTENSION}" STREQUAL "tgz") string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") - else() + elseif ("${TMP_EXTENSION}" STREQUAL "gz") string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") + elseif ("${TMP_EXTENSION}" STREQUAL "xz") + string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}") endif() execute_process( @@ -138,6 +163,12 @@ WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) + elseif ("${TMP_EXTENSION}" STREQUAL "xz") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) else() message(FATAL_ERROR "Unknown package format.") endif()
--- a/Resources/CMake/GoogleLogConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,197 +0,0 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_LOG) - SET(GOOGLE_LOG_SOURCES_DIR ${CMAKE_BINARY_DIR}/glog-0.3.2) - SET(GOOGLE_LOG_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.2.tar.gz") - SET(GOOGLE_LOG_MD5 "897fbff90d91ea2b6d6e78c8cea641cc") - - if (IS_DIRECTORY "${GOOGLE_LOG_SOURCES_DIR}") - set(FirstRun OFF) - else() - set(FirstRun ON) - endif() - - DownloadPackage(${GOOGLE_LOG_MD5} ${GOOGLE_LOG_URL} "${GOOGLE_LOG_SOURCES_DIR}") - - # Glog 0.3.3 fails to build with old versions of MinGW, such as the - # one installed on our Continuous Integration Server that runs - # Debian Squeeze. We thus stick to Glog 0.3.2 for the time being. - - #SET(GOOGLE_LOG_SOURCES_DIR ${CMAKE_BINARY_DIR}/glog-0.3.3) - #DownloadPackage( - # "a6fd2c22f8996846e34c763422717c18" - # "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.3.tar.gz" - # "${GOOGLE_LOG_SOURCES_DIR}") - - - set(GOOGLE_LOG_HEADERS - ${GOOGLE_LOG_SOURCES_DIR}/src/glog/logging.h - ${GOOGLE_LOG_SOURCES_DIR}/src/glog/raw_logging.h - ${GOOGLE_LOG_SOURCES_DIR}/src/glog/stl_logging.h - ${GOOGLE_LOG_SOURCES_DIR}/src/glog/vlog_is_on.h - ) - - set(ac_google_namespace google) - set(ac_google_start_namespace "namespace google {") - set(ac_google_end_namespace "}") - - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") - set(ac_cv_have_unistd_h 1) - set(ac_cv_have_stdint_h 1) - set(ac_cv_have_systypes_h 0) - set(ac_cv_have_inttypes_h 0) - set(ac_cv_have_libgflags 0) - set(ac_cv_have_uint16_t 1) - set(ac_cv_have_u_int16_t 0) - set(ac_cv_have___uint16 0) - set(ac_cv_cxx_using_operator 1) - set(ac_cv_have___builtin_expect 1) - else() - set(ac_cv_have_unistd_h 0) - set(ac_cv_have_stdint_h 0) - set(ac_cv_have_systypes_h 0) - set(ac_cv_have_inttypes_h 0) - set(ac_cv_have_libgflags 0) - set(ac_cv_have_uint16_t 0) - set(ac_cv_have_u_int16_t 0) - set(ac_cv_have___uint16 1) - set(ac_cv_cxx_using_operator 1) - set(ac_cv_have___builtin_expect 0) - endif() - - foreach (f ${GOOGLE_LOG_HEADERS}) - configure_file(${f}.in ${f}) - endforeach() - - include_directories( - ${GOOGLE_LOG_SOURCES_DIR}/src - ) - - if (CMAKE_COMPILER_IS_GNUCXX) - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - execute_process( - COMMAND ${PATCH_EXECUTABLE} -N utilities.cc -i ${ORTHANC_ROOT}/Resources/Patches/glog-utilities-lsb.diff - WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src - RESULT_VARIABLE Failure - ) - else() - execute_process( - COMMAND ${PATCH_EXECUTABLE} -N utilities.cc -i ${ORTHANC_ROOT}/Resources/Patches/glog-utilities.diff - WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src - RESULT_VARIABLE Failure - ) - endif() - - if (Failure AND FirstRun) - message(FATAL_ERROR "Error while patching a file") - endif() - - # Patches for MinGW - execute_process( - #COMMAND ${PATCH_EXECUTABLE} -N port.h -i ${ORTHANC_ROOT}/Resources/Patches/glog-port-h.diff - COMMAND ${PATCH_EXECUTABLE} -N port.h -i ${ORTHANC_ROOT}/Resources/Patches/glog-port-h-v2.diff - WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows - RESULT_VARIABLE Failure - ) - - if (Failure AND FirstRun) - message(FATAL_ERROR "Error while patching a file") - endif() - - execute_process( - COMMAND ${PATCH_EXECUTABLE} -N port.cc -i ${ORTHANC_ROOT}/Resources/Patches/glog-port-cc.diff - WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows - RESULT_VARIABLE Failure - ) - - if (Failure AND FirstRun) - message(FATAL_ERROR "Error while patching a file") - endif() - - elseif (MSVC) - # https://code.google.com/p/google-glog/issues/detail?id=117 - configure_file( - ${ORTHANC_ROOT}/Resources/Patches/glog-visual-studio-port.h - ${GOOGLE_LOG_SOURCES_DIR}/src/windows/port.h - COPYONLY) - - endif() - - - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - # Install the specific configuration for LSB SDK - configure_file( - ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationLSB.h - ${GOOGLE_LOG_SOURCES_DIR}/src/config.h - COPYONLY) - elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") - # Install the specific configuration for Mac OS - configure_file( - ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationDarwin.h - ${GOOGLE_LOG_SOURCES_DIR}/src/config.h - COPYONLY) - else() - configure_file( - ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.h - ${GOOGLE_LOG_SOURCES_DIR}/src/config.h - COPYONLY) - endif() - - set(GOOGLE_LOG_SOURCES - ${GOOGLE_LOG_SOURCES_DIR}/src/demangle.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/logging.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/raw_logging.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/signalhandler.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/symbolize.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/utilities.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/vlog_is_on.cc - ) - - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - include_directories( - ${GOOGLE_LOG_SOURCES_DIR}/src/windows - ) - - set(GOOGLE_LOG_SOURCES - ${GOOGLE_LOG_SOURCES_DIR}/src/windows/port.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/logging.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/raw_logging.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/utilities.cc - ${GOOGLE_LOG_SOURCES_DIR}/src/vlog_is_on.cc - ) - - add_definitions( - -DGLOG_NO_ABBREVIATED_SEVERITIES=1 - -DNO_FRAME_POINTER=1 - -DGOOGLE_GLOG_DLL_DECL= - ) - - if (CMAKE_COMPILER_IS_GNUCXX) - # This is a patch for MinGW64 - add_definitions(-D_TIME_H__S=1) - endif() - endif() - -else() - CHECK_INCLUDE_FILE_CXX(glog/logging.h HAVE_GOOGLE_LOG_H) - if (NOT HAVE_GOOGLE_LOG_H) - message(FATAL_ERROR "Please install the libgoogle-glog-dev package") - endif() - - link_libraries(glog) -endif() - - -CHECK_INCLUDE_FILES(sec_api/string_s.h HAVE_SECURE_STRING_EXTENSIONS) -if (HAVE_SECURE_STRING_EXTENSIONS) - add_definitions(-DHAVE_SECURE_STRING_EXTENSIONS=1) -else() - add_definitions(-DHAVE_SECURE_STRING_EXTENSIONS=0) -endif() - -add_definitions(-DORTHANC_ENABLE_GOOGLE_LOG=1)
--- a/Resources/CMake/GoogleLogConfiguration.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -/* src/config.h. Generated from config.h.in by configure. */ -/* src/config.h.in. Generated from configure.ac by autoheader. */ - -/* Namespace for Google classes */ -#define GOOGLE_NAMESPACE google - -/* Define if you have the `dladdr' function */ -/* #undef HAVE_DLADDR */ - -/* Define to 1 if you have the <dlfcn.h> header file. */ -#define HAVE_DLFCN_H 1 - -/* Define to 1 if you have the <execinfo.h> header file. */ -#define HAVE_EXECINFO_H 1 - -/* Define if you have the `fcntl' function */ -#define HAVE_FCNTL 1 - -/* Define to 1 if you have the <glob.h> header file. */ -#define HAVE_GLOB_H 1 - -/* Define to 1 if you have the <inttypes.h> header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the `pthread' library (-lpthread). */ -#define HAVE_LIBPTHREAD 1 - -/* Define to 1 if you have the <libunwind.h> header file. */ -/* #undef HAVE_LIBUNWIND_H */ - -/* define if you have google gflags library */ -/* #undef HAVE_LIB_GFLAGS */ - -/* define if you have google gmock library */ -/* #undef HAVE_LIB_GMOCK */ - -/* define if you have google gtest library */ -/* #undef HAVE_LIB_GTEST */ - -/* define if you have libunwind */ -/* #undef HAVE_LIB_UNWIND */ - -/* Define to 1 if you have the <memory.h> header file. */ -#define HAVE_MEMORY_H 1 - -/* define if the compiler implements namespaces */ -#define HAVE_NAMESPACES 1 - -/* Define if you have POSIX threads libraries and header files. */ -#define HAVE_PTHREAD 1 - -/* Define to 1 if you have the <pwd.h> header file. */ -#define HAVE_PWD_H 1 - -/* define if the compiler implements pthread_rwlock_* */ -#define HAVE_RWLOCK 1 - -/* Define if you have the `sigaltstack' function */ -#define HAVE_SIGALTSTACK 1 - -/* Define to 1 if you have the <stdint.h> header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the <stdlib.h> header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the <strings.h> header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the <string.h> header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the <syscall.h> header file. */ -#define HAVE_SYSCALL_H 1 - -/* Define to 1 if you have the <syslog.h> header file. */ -#define HAVE_SYSLOG_H 1 - -/* Define to 1 if you have the <sys/stat.h> header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the <sys/syscall.h> header file. */ -#define HAVE_SYS_SYSCALL_H 1 - -/* Define to 1 if you have the <sys/time.h> header file. */ -#define HAVE_SYS_TIME_H 1 - -/* Define to 1 if you have the <sys/types.h> header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the <sys/ucontext.h> header file. */ -#define HAVE_SYS_UCONTEXT_H 1 - -/* Define to 1 if you have the <sys/utsname.h> header file. */ -#define HAVE_SYS_UTSNAME_H 1 - -/* Define to 1 if you have the <ucontext.h> header file. */ -#define HAVE_UCONTEXT_H 1 - -/* Define to 1 if you have the <unistd.h> header file. */ -#define HAVE_UNISTD_H 1 - -/* define if the compiler supports using expression for operator */ -#define HAVE_USING_OPERATOR 1 - -/* define if your compiler has __attribute__ */ -#define HAVE___ATTRIBUTE__ 1 - -/* define if your compiler has __builtin_expect */ -#define HAVE___BUILTIN_EXPECT 1 - -/* define if your compiler has __sync_val_compare_and_swap */ -#define HAVE___SYNC_VAL_COMPARE_AND_SWAP 1 - -/* Define to the sub-directory in which libtool stores uninstalled libraries. - */ -#define LT_OBJDIR ".libs/" - -/* Name of package */ -#define PACKAGE "glog" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "opensource@google.com" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "glog" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "glog 0.3.2" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "glog" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "0.3.2" - -/* How to access the PC from a struct ucontext */ -/*#include <ucontext.h> -#include <sys/ucontext.h> -#ifdef REG_RIP -#define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP] -#else -#undef PC_FROM_UCONTEXT -#endif*/ - -// This is required for older versions of Linux -#undef PC_FROM_UCONTEXT - -/* Define to necessary symbol if this constant uses a non-standard name on - your system. */ -/* #undef PTHREAD_CREATE_JOINABLE */ - -/* The size of `void *', as computed by sizeof. */ -#define SIZEOF_VOID_P 8 - -/* Define to 1 if you have the ANSI C header files. */ -/* #undef STDC_HEADERS */ - -/* the namespace where STL code like vector<> is defined */ -#define STL_NAMESPACE std - -/* location of source code */ -#define TEST_SRC_DIR "." - -/* Version number of package */ -#define VERSION "0.3.2" - -/* Stops putting the code inside the Google namespace */ -#define _END_GOOGLE_NAMESPACE_ } - -/* Puts following code inside the Google namespace */ -#define _START_GOOGLE_NAMESPACE_ namespace google {
--- a/Resources/CMake/GoogleLogConfigurationDarwin.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -/* src/config.h. Generated from config.h.in by configure. */ -/* src/config.h.in. Generated from configure.ac by autoheader. */ - -/* Namespace for Google classes */ -#define GOOGLE_NAMESPACE google - -/* Define if you have the `dladdr' function */ -/* #undef HAVE_DLADDR */ - -/* Define to 1 if you have the <dlfcn.h> header file. */ -#define HAVE_DLFCN_H 1 - -/* Define to 1 if you have the <execinfo.h> header file. */ -#define HAVE_EXECINFO_H 1 - -/* Define if you have the `fcntl' function */ -#define HAVE_FCNTL 1 - -/* Define to 1 if you have the <glob.h> header file. */ -#define HAVE_GLOB_H 1 - -/* Define to 1 if you have the <inttypes.h> header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the `pthread' library (-lpthread). */ -#define HAVE_LIBPTHREAD 1 - -/* Define to 1 if you have the <libunwind.h> header file. */ -/* #undef HAVE_LIBUNWIND_H */ - -/* define if you have google gflags library */ -/* #undef HAVE_LIB_GFLAGS */ - -/* define if you have google gmock library */ -/* #undef HAVE_LIB_GMOCK */ - -/* define if you have google gtest library */ -/* #undef HAVE_LIB_GTEST */ - -/* define if you have libunwind */ -/* #undef HAVE_LIB_UNWIND */ - -/* Define to 1 if you have the <memory.h> header file. */ -#define HAVE_MEMORY_H 1 - -/* define if the compiler implements namespaces */ -#define HAVE_NAMESPACES 1 - -/* Define if you have POSIX threads libraries and header files. */ -#define HAVE_PTHREAD 1 - -/* Define to 1 if you have the <pwd.h> header file. */ -#define HAVE_PWD_H 1 - -/* define if the compiler implements pthread_rwlock_* */ -#define HAVE_RWLOCK 1 - -/* Define if you have the `sigaltstack' function */ -#define HAVE_SIGALTSTACK 1 - -/* Define to 1 if you have the <stdint.h> header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the <stdlib.h> header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the <strings.h> header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the <string.h> header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the <syscall.h> header file. */ -/* #undef HAVE_SYSCALL_H */ - -/* Define to 1 if you have the <syslog.h> header file. */ -#define HAVE_SYSLOG_H 1 - -/* Define to 1 if you have the <sys/stat.h> header file. */ -/* #define HAVE_SYS_STAT_H 1 */ - -/* Define to 1 if you have the <sys/syscall.h> header file. */ -#define HAVE_SYS_SYSCALL_H 1 - -/* Define to 1 if you have the <sys/time.h> header file. */ -#define HAVE_SYS_TIME_H 1 - -/* Define to 1 if you have the <sys/types.h> header file. */ -/* #define HAVE_SYS_TYPES_H 1 */ - -/* Define to 1 if you have the <sys/ucontext.h> header file. */ -/* #define HAVE_SYS_UCONTEXT_H 1 */ - -/* Define to 1 if you have the <sys/utsname.h> header file. */ -#define HAVE_SYS_UTSNAME_H 1 - -/* Define to 1 if you have the <ucontext.h> header file. */ -#define HAVE_UCONTEXT_H 1 - -/* Define to 1 if you have the <unistd.h> header file. */ -#define HAVE_UNISTD_H 1 - -/* define if the compiler supports using expression for operator */ -#define HAVE_USING_OPERATOR 1 - -/* define if your compiler has __attribute__ */ -#define HAVE___ATTRIBUTE__ 1 - -/* define if your compiler has __builtin_expect */ -#define HAVE___BUILTIN_EXPECT 1 - -/* define if your compiler has __sync_val_compare_and_swap */ -#define HAVE___SYNC_VAL_COMPARE_AND_SWAP 1 - -/* Define to the sub-directory in which libtool stores uninstalled libraries. - */ -#define LT_OBJDIR ".libs/" - -/* Name of package */ -#define PACKAGE "glog" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "opensource@google.com" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "glog" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "glog 0.3.2" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "glog" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "0.3.2" - -/* How to access the PC from a struct ucontext */ -/*#include <ucontext.h> -#include <sys/ucontext.h> -#ifdef REG_RIP -#define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP] -#else -#undef PC_FROM_UCONTEXT -#endif*/ - -// This is required for older versions of Linux -#undef PC_FROM_UCONTEXT - -/* Define to necessary symbol if this constant uses a non-standard name on - your system. */ -/* #undef PTHREAD_CREATE_JOINABLE */ - -/* The size of `void *', as computed by sizeof. */ -#define SIZEOF_VOID_P 8 - -/* Define to 1 if you have the ANSI C header files. */ -/* #undef STDC_HEADERS */ - -/* the namespace where STL code like vector<> is defined */ -#define STL_NAMESPACE std - -/* location of source code */ -#define TEST_SRC_DIR "." - -/* Version number of package */ -#define VERSION "0.3.2" - -/* Stops putting the code inside the Google namespace */ -#define _END_GOOGLE_NAMESPACE_ } - -/* Puts following code inside the Google namespace */ -#define _START_GOOGLE_NAMESPACE_ namespace google {
--- a/Resources/CMake/GoogleLogConfigurationLSB.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -/* src/config.h. Generated from config.h.in by configure. */ -/* src/config.h.in. Generated from configure.ac by autoheader. */ - -/* Namespace for Google classes */ -#define GOOGLE_NAMESPACE google - -/* Define if you have the `dladdr' function */ -/* #undef HAVE_DLADDR */ - -/* Define to 1 if you have the <dlfcn.h> header file. */ -#define HAVE_DLFCN_H 1 - -/* Define to 1 if you have the <execinfo.h> header file. */ -#define HAVE_EXECINFO_H 1 - -/* Define if you have the `fcntl' function */ -#define HAVE_FCNTL 1 - -/* Define to 1 if you have the <glob.h> header file. */ -#define HAVE_GLOB_H 1 - -/* Define to 1 if you have the <inttypes.h> header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the `pthread' library (-lpthread). */ -#define HAVE_LIBPTHREAD 1 - -/* Define to 1 if you have the <libunwind.h> header file. */ -/* #undef HAVE_LIBUNWIND_H */ - -/* define if you have google gflags library */ -/* #undef HAVE_LIB_GFLAGS */ - -/* define if you have google gmock library */ -/* #undef HAVE_LIB_GMOCK */ - -/* define if you have google gtest library */ -/* #undef HAVE_LIB_GTEST */ - -/* define if you have libunwind */ -/* #undef HAVE_LIB_UNWIND */ - -/* Define to 1 if you have the <memory.h> header file. */ -#define HAVE_MEMORY_H 1 - -/* define if the compiler implements namespaces */ -#define HAVE_NAMESPACES 1 - -/* Define if you have POSIX threads libraries and header files. */ -#define HAVE_PTHREAD 1 - -/* Define to 1 if you have the <pwd.h> header file. */ -#define HAVE_PWD_H 1 - -/* define if the compiler implements pthread_rwlock_* */ -#define HAVE_RWLOCK 1 - -/* Define if you have the `sigaltstack' function */ -#define HAVE_SIGALTSTACK 1 - -/* Define to 1 if you have the <stdint.h> header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the <stdlib.h> header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the <strings.h> header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the <string.h> header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the <syscall.h> header file. */ -/* #undef HAVE_SYSCALL_H */ - -/* Define to 1 if you have the <syslog.h> header file. */ -#define HAVE_SYSLOG_H 1 - -/* Define to 1 if you have the <sys/stat.h> header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the <sys/syscall.h> header file. */ -/* #undef HAVE_SYS_SYSCALL_H */ - -/* Define to 1 if you have the <sys/time.h> header file. */ -#define HAVE_SYS_TIME_H 1 - -/* Define to 1 if you have the <sys/types.h> header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the <sys/ucontext.h> header file. */ -/* #define HAVE_SYS_UCONTEXT_H 1 */ - -/* Define to 1 if you have the <sys/utsname.h> header file. */ -#define HAVE_SYS_UTSNAME_H 1 - -/* Define to 1 if you have the <ucontext.h> header file. */ -/* #define HAVE_UCONTEXT_H 1 */ - -/* Define to 1 if you have the <unistd.h> header file. */ -#define HAVE_UNISTD_H 1 - -/* define if the compiler supports using expression for operator */ -#define HAVE_USING_OPERATOR 1 - -/* define if your compiler has __attribute__ */ -#define HAVE___ATTRIBUTE__ 1 - -/* define if your compiler has __builtin_expect */ -#define HAVE___BUILTIN_EXPECT 1 - -/* define if your compiler has __sync_val_compare_and_swap */ -#define HAVE___SYNC_VAL_COMPARE_AND_SWAP 1 - -/* Define to the sub-directory in which libtool stores uninstalled libraries. - */ -#define LT_OBJDIR ".libs/" - -/* Name of package */ -#define PACKAGE "glog" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "opensource@google.com" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "glog" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "glog 0.3.2" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "glog" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "0.3.2" - -/* How to access the PC from a struct ucontext */ -/*#include <ucontext.h> -#include <sys/ucontext.h> -#ifdef REG_RIP -#define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP] -#else -#undef PC_FROM_UCONTEXT -#endif*/ - -// This is required for older versions of Linux -#undef PC_FROM_UCONTEXT - -/* Define to necessary symbol if this constant uses a non-standard name on - your system. */ -/* #undef PTHREAD_CREATE_JOINABLE */ - -/* The size of `void *', as computed by sizeof. */ -#define SIZEOF_VOID_P 8 - -/* Define to 1 if you have the ANSI C header files. */ -/* #undef STDC_HEADERS */ - -/* the namespace where STL code like vector<> is defined */ -#define STL_NAMESPACE std - -/* location of source code */ -#define TEST_SRC_DIR "." - -/* Version number of package */ -#define VERSION "0.3.2" - -/* Stops putting the code inside the Google namespace */ -#define _END_GOOGLE_NAMESPACE_ } - -/* Puts following code inside the Google namespace */ -#define _START_GOOGLE_NAMESPACE_ namespace google {
--- a/Resources/CMake/GoogleTestConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/GoogleTestConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,32 +1,58 @@ -if (USE_GTEST_DEBIAN_SOURCE_PACKAGE) - set(GTEST_SOURCES /usr/src/gtest/src/gtest-all.cc) - include_directories(/usr/src/gtest) +if (USE_GOOGLE_TEST_DEBIAN_PACKAGE) + find_path(GOOGLE_TEST_DEBIAN_SOURCES_DIR + NAMES src/gtest-all.cc + PATHS + /usr/src/gtest + /usr/src/googletest/googletest + PATH_SUFFIXES src + ) - if (NOT EXISTS /usr/include/gtest/gtest.h OR - NOT EXISTS ${GTEST_SOURCES}) + find_path(GOOGLE_TEST_DEBIAN_INCLUDE_DIR + NAMES gtest.h + PATHS + /usr/include/gtest + ) + + message("Path to the Debian Google Test sources: ${GOOGLE_TEST_DEBIAN_SOURCES_DIR}") + message("Path to the Debian Google Test includes: ${GOOGLE_TEST_DEBIAN_INCLUDE_DIR}") + + set(GOOGLE_TEST_SOURCES + ${GOOGLE_TEST_DEBIAN_SOURCES_DIR}/src/gtest-all.cc + ) + + include_directories(${GOOGLE_TEST_DEBIAN_SOURCES_DIR}) + + if (NOT EXISTS ${GOOGLE_TEST_SOURCES} OR + NOT EXISTS ${GOOGLE_TEST_DEBIAN_INCLUDE_DIR}/gtest.h) message(FATAL_ERROR "Please install the libgtest-dev package") endif() elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST) - set(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0) - set(GTEST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.7.0.zip") - set(GTEST_MD5 "2d6ec8ccdf5c46b05ba54a9fd1d130d7") + set(GOOGLE_TEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0) + set(GOOGLE_TEST_URL "http://www.orthanc-server.com/downloads/third-party/gtest-1.7.0.zip") + set(GOOGLE_TEST_MD5 "2d6ec8ccdf5c46b05ba54a9fd1d130d7") - DownloadPackage(${GTEST_MD5} ${GTEST_URL} "${GTEST_SOURCES_DIR}") + DownloadPackage(${GOOGLE_TEST_MD5} ${GOOGLE_TEST_URL} "${GOOGLE_TEST_SOURCES_DIR}") include_directories( - ${GTEST_SOURCES_DIR}/include - ${GTEST_SOURCES_DIR} + ${GOOGLE_TEST_SOURCES_DIR}/include + ${GOOGLE_TEST_SOURCES_DIR} ) - set(GTEST_SOURCES - ${GTEST_SOURCES_DIR}/src/gtest-all.cc + set(GOOGLE_TEST_SOURCES + ${GOOGLE_TEST_SOURCES_DIR}/src/gtest-all.cc ) # https://code.google.com/p/googletest/issues/detail?id=412 if (MSVC) # VS2012 does not support tuples correctly yet add_definitions(/D _VARIADIC_MAX=10) endif() + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + add_definitions(-DGTEST_HAS_CLONE=0) + endif() + + source_group(ThirdParty\\GoogleTest REGULAR_EXPRESSION ${GOOGLE_TEST_SOURCES_DIR}/.*) else() include(FindGTest) @@ -35,5 +61,8 @@ endif() include_directories(${GTEST_INCLUDE_DIRS}) - link_libraries(${GTEST_LIBRARIES}) + + # The variable GTEST_LIBRARIES contains the shared library of + # Google Test, create an alias for more uniformity + set(GOOGLE_TEST_LIBRARIES ${GTEST_LIBRARIES}) endif()
--- a/Resources/CMake/JsonCppConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/JsonCppConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,18 @@ +set(JSONCPP_CXX11 OFF) + if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP) - set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.5) - set(JSONCPP_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-0.10.5.tar.gz") - set(JSONCPP_MD5 "db146bac5a126ded9bd728ab7b61ed6b") + if (USE_LEGACY_JSONCPP) + set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.6) + set(JSONCPP_URL "http://www.orthanc-server.com/downloads/third-party/jsoncpp-0.10.6.tar.gz") + set(JSONCPP_MD5 "13d1991d79697df8cadbc25c93e37c83") + add_definitions(-DORTHANC_LEGACY_JSONCPP=1) + else() + set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-1.8.4) + set(JSONCPP_URL "http://www.orthanc-server.com/downloads/third-party/jsoncpp-1.8.4.tar.gz") + set(JSONCPP_MD5 "fa47a3ab6b381869b6a5f20811198662") + add_definitions(-DORTHANC_LEGACY_JSONCPP=0) + set(JSONCPP_CXX11 ON) + endif() DownloadPackage(${JSONCPP_MD5} ${JSONCPP_URL} "${JSONCPP_SOURCES_DIR}") @@ -15,6 +26,10 @@ ${JSONCPP_SOURCES_DIR}/include ) + if (NOT ENABLE_LOCALE) + add_definitions(-DJSONCPP_NO_LOCALE_SUPPORT=1) + endif() + source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*) else() @@ -32,4 +47,40 @@ message(FATAL_ERROR "Please install the libjsoncpp-dev package") endif() + # Switch to the C++11 standard if the version of JsonCpp is 1.y.z + if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h) + file(STRINGS + "${JSONCPP_INCLUDE_DIR}/json/version.h" + JSONCPP_VERSION_MAJOR1 REGEX + ".*define JSONCPP_VERSION_MAJOR.*") + + if (NOT JSONCPP_VERSION_MAJOR1) + message(FATAL_ERROR "Unable to extract the major version of JsonCpp") + endif() + + string(REGEX REPLACE + ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" + JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1}) + message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}") + + if (JSONCPP_VERSION_MAJOR GREATER 0) + set(JSONCPP_CXX11 ON) + endif() + else() + message("Unable to detect the major version of JsonCpp, assuming < 1.0.0") + endif() endif() + + +if (JSONCPP_CXX11) + # Osimis has encountered problems when this macro is left at its + # default value (1000), so we increase this limit + # 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") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations") + endif() +endif()
--- a/Resources/CMake/LibCurlConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/LibCurlConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,15 +1,35 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_CURL) - SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.44.0) - SET(CURL_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.44.0.tar.gz") - SET(CURL_MD5 "cf46112b5151e2f1a3fd38439bdade23") + SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.57.0) + SET(CURL_URL "http://www.orthanc-server.com/downloads/third-party/curl-7.57.0.tar.gz") + SET(CURL_MD5 "c7aab73aaf5e883ca1d7518f93649dc2") + if (IS_DIRECTORY "${CURL_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) + endif() + DownloadPackage(${CURL_MD5} ${CURL_URL} "${CURL_SOURCES_DIR}") + if (FirstRun) + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${ORTHANC_ROOT}/Resources/Patches/curl-7.57.0-cmake.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() + endif() + include_directories( ${CURL_SOURCES_DIR}/include ) AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES) + AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vauth CURL_SOURCES) AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vtls CURL_SOURCES) source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*) @@ -35,22 +55,32 @@ add_definitions( #-DHAVE_LIBSSL=1 -DUSE_OPENSSL=1 + -DHAVE_OPENSSL_ENGINE_H=1 -DUSE_SSLEAY=1 ) endif() - file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "") + if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/curl_config.h") + #file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "") + + file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h "#include \"../vauth.h\"\n") + file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/digest.h "#include \"../digest.h\"\n") + file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/ntlm.h "#include \"../ntlm.h\"\n") + file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vtls/vtls.h "#include \"../../vtls/vtls.h\"\n") - file(GLOB CURL_LIBS_HEADERS ${CURL_SOURCES_DIR}/lib/*.h) - foreach (header IN LISTS CURL_LIBS_HEADERS) - get_filename_component(filename ${header} NAME) - file(WRITE ${CURL_SOURCES_DIR}/lib/vtls/${filename} "#include \"../${filename}\"\n") - endforeach() + file(GLOB CURL_LIBS_HEADERS ${CURL_SOURCES_DIR}/lib/*.h) + foreach (header IN LISTS CURL_LIBS_HEADERS) + get_filename_component(filename ${header} NAME) + file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/${filename} "#include \"../${filename}\"\n") + file(WRITE ${CURL_SOURCES_DIR}/lib/vtls/${filename} "#include \"../${filename}\"\n") + endforeach() + endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") SET(TMP_OS "x86_64") else() @@ -59,48 +89,229 @@ set_property( SOURCE ${CURL_SOURCES} - PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;HAVE_LONGLONG;OS=\"${TMP_OS}\"" + PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H=1;OS=\"${TMP_OS}\"" ) + + include(${CURL_SOURCES_DIR}/CMake/Macros.cmake) + + # WARNING: Do *not* reorder the "check_include_file_concat()" below! + check_include_file_concat("stdio.h" HAVE_STDIO_H) + check_include_file_concat("inttypes.h" HAVE_INTTYPES_H) + check_include_file_concat("sys/filio.h" HAVE_SYS_FILIO_H) + check_include_file_concat("sys/ioctl.h" HAVE_SYS_IOCTL_H) + check_include_file_concat("sys/param.h" HAVE_SYS_PARAM_H) + check_include_file_concat("sys/poll.h" HAVE_SYS_POLL_H) + check_include_file_concat("sys/resource.h" HAVE_SYS_RESOURCE_H) + check_include_file_concat("sys/select.h" HAVE_SYS_SELECT_H) + check_include_file_concat("sys/socket.h" HAVE_SYS_SOCKET_H) + check_include_file_concat("sys/sockio.h" HAVE_SYS_SOCKIO_H) + check_include_file_concat("sys/stat.h" HAVE_SYS_STAT_H) + check_include_file_concat("sys/time.h" HAVE_SYS_TIME_H) + check_include_file_concat("sys/types.h" HAVE_SYS_TYPES_H) + check_include_file_concat("sys/uio.h" HAVE_SYS_UIO_H) + check_include_file_concat("sys/un.h" HAVE_SYS_UN_H) + check_include_file_concat("sys/utime.h" HAVE_SYS_UTIME_H) + check_include_file_concat("sys/xattr.h" HAVE_SYS_XATTR_H) + check_include_file_concat("alloca.h" HAVE_ALLOCA_H) + check_include_file_concat("arpa/inet.h" HAVE_ARPA_INET_H) + check_include_file_concat("arpa/tftp.h" HAVE_ARPA_TFTP_H) + check_include_file_concat("assert.h" HAVE_ASSERT_H) + check_include_file_concat("crypto.h" HAVE_CRYPTO_H) + check_include_file_concat("des.h" HAVE_DES_H) + check_include_file_concat("err.h" HAVE_ERR_H) + check_include_file_concat("errno.h" HAVE_ERRNO_H) + check_include_file_concat("fcntl.h" HAVE_FCNTL_H) + check_include_file_concat("idn2.h" HAVE_IDN2_H) + check_include_file_concat("ifaddrs.h" HAVE_IFADDRS_H) + check_include_file_concat("io.h" HAVE_IO_H) + check_include_file_concat("krb.h" HAVE_KRB_H) + check_include_file_concat("libgen.h" HAVE_LIBGEN_H) + check_include_file_concat("limits.h" HAVE_LIMITS_H) + check_include_file_concat("locale.h" HAVE_LOCALE_H) + check_include_file_concat("net/if.h" HAVE_NET_IF_H) + check_include_file_concat("netdb.h" HAVE_NETDB_H) + check_include_file_concat("netinet/in.h" HAVE_NETINET_IN_H) + check_include_file_concat("netinet/tcp.h" HAVE_NETINET_TCP_H) + + check_include_file_concat("pem.h" HAVE_PEM_H) + check_include_file_concat("poll.h" HAVE_POLL_H) + check_include_file_concat("pwd.h" HAVE_PWD_H) + check_include_file_concat("rsa.h" HAVE_RSA_H) + check_include_file_concat("setjmp.h" HAVE_SETJMP_H) + check_include_file_concat("sgtty.h" HAVE_SGTTY_H) + check_include_file_concat("signal.h" HAVE_SIGNAL_H) + check_include_file_concat("ssl.h" HAVE_SSL_H) + check_include_file_concat("stdbool.h" HAVE_STDBOOL_H) + check_include_file_concat("stdint.h" HAVE_STDINT_H) + check_include_file_concat("stdio.h" HAVE_STDIO_H) + check_include_file_concat("stdlib.h" HAVE_STDLIB_H) + check_include_file_concat("string.h" HAVE_STRING_H) + check_include_file_concat("strings.h" HAVE_STRINGS_H) + check_include_file_concat("stropts.h" HAVE_STROPTS_H) + check_include_file_concat("termio.h" HAVE_TERMIO_H) + check_include_file_concat("termios.h" HAVE_TERMIOS_H) + check_include_file_concat("time.h" HAVE_TIME_H) + check_include_file_concat("unistd.h" HAVE_UNISTD_H) + check_include_file_concat("utime.h" HAVE_UTIME_H) + check_include_file_concat("x509.h" HAVE_X509_H) + + check_include_file_concat("process.h" HAVE_PROCESS_H) + check_include_file_concat("stddef.h" HAVE_STDDEF_H) + check_include_file_concat("dlfcn.h" HAVE_DLFCN_H) + check_include_file_concat("malloc.h" HAVE_MALLOC_H) + check_include_file_concat("memory.h" HAVE_MEMORY_H) + check_include_file_concat("netinet/if_ether.h" HAVE_NETINET_IF_ETHER_H) + check_include_file_concat("stdint.h" HAVE_STDINT_H) + check_include_file_concat("sockio.h" HAVE_SOCKIO_H) + check_include_file_concat("sys/utsname.h" HAVE_SYS_UTSNAME_H) + + check_type_size("size_t" SIZEOF_SIZE_T) + check_type_size("ssize_t" SIZEOF_SSIZE_T) + check_type_size("long long" SIZEOF_LONG_LONG) + check_type_size("long" SIZEOF_LONG) + check_type_size("short" SIZEOF_SHORT) + check_type_size("int" SIZEOF_INT) + check_type_size("__int64" SIZEOF___INT64) + check_type_size("long double" SIZEOF_LONG_DOUBLE) + check_type_size("time_t" SIZEOF_TIME_T) + check_type_size("off_t" SIZEOF_OFF_T) + check_type_size("socklen_t" CURL_SIZEOF_CURL_SOCKLEN_T) - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - add_definitions( - -DRECV_TYPE_ARG1=int - -DRECV_TYPE_ARG2=void* - -DRECV_TYPE_ARG3=size_t - -DRECV_TYPE_ARG4=int - -DRECV_TYPE_RETV=ssize_t - -DSEND_TYPE_ARG1=int - -DSEND_TYPE_ARG2=void* - -DSEND_QUAL_ARG2=const - -DSEND_TYPE_ARG3=size_t - -DSEND_TYPE_ARG4=int - -DSEND_TYPE_RETV=ssize_t - -DSIZEOF_SHORT=2 - -DSIZEOF_INT=4 - -DSIZEOF_SIZE_T=8 + check_symbol_exists(basename "${CURL_INCLUDES}" HAVE_BASENAME) + check_symbol_exists(socket "${CURL_INCLUDES}" HAVE_SOCKET) + # poll on macOS is unreliable, it first did not exist, then was broken until + # fixed in 10.9 only to break again in 10.12. + if(NOT APPLE) + check_symbol_exists(poll "${CURL_INCLUDES}" HAVE_POLL) + endif() + check_symbol_exists(select "${CURL_INCLUDES}" HAVE_SELECT) + check_symbol_exists(strdup "${CURL_INCLUDES}" HAVE_STRDUP) + check_symbol_exists(strstr "${CURL_INCLUDES}" HAVE_STRSTR) + check_symbol_exists(strtok_r "${CURL_INCLUDES}" HAVE_STRTOK_R) + check_symbol_exists(strftime "${CURL_INCLUDES}" HAVE_STRFTIME) + check_symbol_exists(uname "${CURL_INCLUDES}" HAVE_UNAME) + check_symbol_exists(strcasecmp "${CURL_INCLUDES}" HAVE_STRCASECMP) + check_symbol_exists(stricmp "${CURL_INCLUDES}" HAVE_STRICMP) + check_symbol_exists(strcmpi "${CURL_INCLUDES}" HAVE_STRCMPI) + check_symbol_exists(strncmpi "${CURL_INCLUDES}" HAVE_STRNCMPI) + check_symbol_exists(alarm "${CURL_INCLUDES}" HAVE_ALARM) + if(NOT HAVE_STRNCMPI) + set(HAVE_STRCMPI) + endif(NOT HAVE_STRNCMPI) + + check_symbol_exists(gethostbyaddr "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR) + check_symbol_exists(gethostbyaddr_r "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR_R) + check_symbol_exists(gettimeofday "${CURL_INCLUDES}" HAVE_GETTIMEOFDAY) + check_symbol_exists(inet_addr "${CURL_INCLUDES}" HAVE_INET_ADDR) + check_symbol_exists(inet_ntoa "${CURL_INCLUDES}" HAVE_INET_NTOA) + check_symbol_exists(inet_ntoa_r "${CURL_INCLUDES}" HAVE_INET_NTOA_R) + check_symbol_exists(tcsetattr "${CURL_INCLUDES}" HAVE_TCSETATTR) + check_symbol_exists(tcgetattr "${CURL_INCLUDES}" HAVE_TCGETATTR) + check_symbol_exists(perror "${CURL_INCLUDES}" HAVE_PERROR) + check_symbol_exists(closesocket "${CURL_INCLUDES}" HAVE_CLOSESOCKET) + check_symbol_exists(setvbuf "${CURL_INCLUDES}" HAVE_SETVBUF) + check_symbol_exists(sigsetjmp "${CURL_INCLUDES}" HAVE_SIGSETJMP) + check_symbol_exists(getpass_r "${CURL_INCLUDES}" HAVE_GETPASS_R) + check_symbol_exists(strlcat "${CURL_INCLUDES}" HAVE_STRLCAT) + check_symbol_exists(getpwuid "${CURL_INCLUDES}" HAVE_GETPWUID) + check_symbol_exists(geteuid "${CURL_INCLUDES}" HAVE_GETEUID) + check_symbol_exists(utime "${CURL_INCLUDES}" HAVE_UTIME) + check_symbol_exists(gmtime_r "${CURL_INCLUDES}" HAVE_GMTIME_R) + check_symbol_exists(localtime_r "${CURL_INCLUDES}" HAVE_LOCALTIME_R) + + check_symbol_exists(gethostbyname "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME) + check_symbol_exists(gethostbyname_r "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME_R) + + check_symbol_exists(signal "${CURL_INCLUDES}" HAVE_SIGNAL_FUNC) + check_symbol_exists(SIGALRM "${CURL_INCLUDES}" HAVE_SIGNAL_MACRO) + if(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO) + set(HAVE_SIGNAL 1) + endif(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO) + check_symbol_exists(uname "${CURL_INCLUDES}" HAVE_UNAME) + check_symbol_exists(strtoll "${CURL_INCLUDES}" HAVE_STRTOLL) + check_symbol_exists(_strtoi64 "${CURL_INCLUDES}" HAVE__STRTOI64) + check_symbol_exists(strerror_r "${CURL_INCLUDES}" HAVE_STRERROR_R) + check_symbol_exists(siginterrupt "${CURL_INCLUDES}" HAVE_SIGINTERRUPT) + check_symbol_exists(perror "${CURL_INCLUDES}" HAVE_PERROR) + check_symbol_exists(fork "${CURL_INCLUDES}" HAVE_FORK) + check_symbol_exists(getaddrinfo "${CURL_INCLUDES}" HAVE_GETADDRINFO) + check_symbol_exists(freeaddrinfo "${CURL_INCLUDES}" HAVE_FREEADDRINFO) + check_symbol_exists(freeifaddrs "${CURL_INCLUDES}" HAVE_FREEIFADDRS) + check_symbol_exists(pipe "${CURL_INCLUDES}" HAVE_PIPE) + check_symbol_exists(ftruncate "${CURL_INCLUDES}" HAVE_FTRUNCATE) + check_symbol_exists(getprotobyname "${CURL_INCLUDES}" HAVE_GETPROTOBYNAME) + check_symbol_exists(getrlimit "${CURL_INCLUDES}" HAVE_GETRLIMIT) + check_symbol_exists(setlocale "${CURL_INCLUDES}" HAVE_SETLOCALE) + check_symbol_exists(setmode "${CURL_INCLUDES}" HAVE_SETMODE) + check_symbol_exists(setrlimit "${CURL_INCLUDES}" HAVE_SETRLIMIT) + check_symbol_exists(fcntl "${CURL_INCLUDES}" HAVE_FCNTL) + check_symbol_exists(ioctl "${CURL_INCLUDES}" HAVE_IOCTL) + check_symbol_exists(setsockopt "${CURL_INCLUDES}" HAVE_SETSOCKOPT) + + if(HAVE_SIZEOF_LONG_LONG) + set(HAVE_LONGLONG 1) + set(HAVE_LL 1) + endif(HAVE_SIZEOF_LONG_LONG) + + check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME) + check_function_exists(gethostname HAVE_GETHOSTNAME) + + check_include_file_concat("pthread.h" HAVE_PTHREAD_H) + check_symbol_exists(recv "sys/socket.h" HAVE_RECV) + check_symbol_exists(send "sys/socket.h" HAVE_SEND) + + check_struct_has_member("struct sockaddr_un" sun_path "sys/un.h" USE_UNIX_SOCKETS) + + set(CMAKE_REQUIRED_INCLUDES "${CURL_SOURCES_DIR}/include") + set(CMAKE_EXTRA_INCLUDE_FILES "curl/system.h") + check_type_size("curl_off_t" SIZEOF_CURL_OFF_T) + + add_definitions(-DHAVE_GLIBC_STRERROR_R=1) + + include(${CURL_SOURCES_DIR}/CMake/OtherTests.cmake) + + foreach(CURL_TEST + HAVE_FCNTL_O_NONBLOCK + HAVE_IOCTLSOCKET + HAVE_IOCTLSOCKET_CAMEL + HAVE_IOCTLSOCKET_CAMEL_FIONBIO + HAVE_IOCTLSOCKET_FIONBIO + HAVE_IOCTL_FIONBIO + HAVE_IOCTL_SIOCGIFADDR + HAVE_SETSOCKOPT_SO_NONBLOCK + HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + TIME_WITH_SYS_TIME + HAVE_O_NONBLOCK + HAVE_GETHOSTBYADDR_R_5 + HAVE_GETHOSTBYADDR_R_7 + HAVE_GETHOSTBYADDR_R_8 + HAVE_GETHOSTBYADDR_R_5_REENTRANT + HAVE_GETHOSTBYADDR_R_7_REENTRANT + HAVE_GETHOSTBYADDR_R_8_REENTRANT + HAVE_GETHOSTBYNAME_R_3 + HAVE_GETHOSTBYNAME_R_5 + HAVE_GETHOSTBYNAME_R_6 + HAVE_GETHOSTBYNAME_R_3_REENTRANT + HAVE_GETHOSTBYNAME_R_5_REENTRANT + HAVE_GETHOSTBYNAME_R_6_REENTRANT + HAVE_SOCKLEN_T + HAVE_IN_ADDR_T + HAVE_BOOL_T + STDC_HEADERS + RETSIGTYPE_TEST + HAVE_INET_NTOA_R_DECL + HAVE_INET_NTOA_R_DECL_REENTRANT + HAVE_GETADDRINFO + HAVE_FILE_OFFSET_BITS ) - elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") - add_definitions( - -DRECV_TYPE_ARG1=int - -DRECV_TYPE_ARG2=void* - -DRECV_TYPE_ARG3=size_t - -DRECV_TYPE_ARG4=int - -DRECV_TYPE_RETV=int - -DSEND_TYPE_ARG1=int - -DSEND_TYPE_ARG2=void* - -DSEND_QUAL_ARG2=const - -DSEND_TYPE_ARG3=size_t - -DSEND_TYPE_ARG4=int - -DSEND_TYPE_RETV=int - -DSIZEOF_SHORT=2 - -DSIZEOF_INT=4 - -DSIZEOF_SIZE_T=4 - ) - else() - message(FATAL_ERROR "Support your platform here") - endif() + curl_internal_test(${CURL_TEST}) + endforeach(CURL_TEST) + + configure_file( + ${CURL_SOURCES_DIR}/lib/curl_config.h.cmake + ${CURL_SOURCES_DIR}/lib/curl_config.h + ) endif() - else() include(FindCURL) include_directories(${CURL_INCLUDE_DIRS})
--- a/Resources/CMake/LibIconvConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/LibIconvConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,50 +1,98 @@ -set(LIBICONV_SOURCES_DIR ${CMAKE_BINARY_DIR}/libiconv-1.14) -set(LIBICONV_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libiconv-1.14.tar.gz") -set(LIBICONV_MD5 "e34509b1623cec449dfeb73d7ce9c6c6") +if (NOT ENABLE_LOCALE) + message("Support for locales is disabled") + +elseif (NOT USE_BOOST_ICONV) + message("Not using libiconv") -DownloadPackage(${LIBICONV_MD5} ${LIBICONV_URL} "${LIBICONV_SOURCES_DIR}") +else() + message("Using libiconv") + + if (STATIC_BUILD OR NOT USE_SYSTEM_LIBICONV) + set(LIBICONV_SOURCES_DIR ${CMAKE_BINARY_DIR}/libiconv-1.15) + set(LIBICONV_URL "http://www.orthanc-server.com/downloads/third-party/libiconv-1.15.tar.gz") + set(LIBICONV_MD5 "ace8b5f2db42f7b3b3057585e80d9808") + + DownloadPackage(${LIBICONV_MD5} ${LIBICONV_URL} "${LIBICONV_SOURCES_DIR}") -# https://groups.google.com/d/msg/android-ndk/AS1nkxnk6m4/EQm09hD1tigJ -add_definitions( - -DBOOST_LOCALE_WITH_ICONV=1 - -DBUILDING_LIBICONV=1 - -DIN_LIBRARY=1 - -DLIBDIR="" - -DICONV_CONST= - ) + # Disable the support of libiconv that is shipped by default with + # the C standard library on Linux. Setting this macro redirects + # calls from "iconv*()" to "libiconv*()" by defining macros in the + # C headers of "libiconv-1.15". + add_definitions(-DLIBICONV_PLUG=1) -configure_file( - ${LIBICONV_SOURCES_DIR}/srclib/localcharset.h - ${LIBICONV_SOURCES_DIR}/include - COPYONLY) + # https://groups.google.com/d/msg/android-ndk/AS1nkxnk6m4/EQm09hD1tigJ + add_definitions( + -DBUILDING_LIBICONV=1 + -DIN_LIBRARY=1 + -DLIBDIR="" + -DICONV_CONST= + ) + + configure_file( + ${LIBICONV_SOURCES_DIR}/srclib/localcharset.h + ${LIBICONV_SOURCES_DIR}/include + COPYONLY) -set(HAVE_VISIBILITY 0) -set(ICONV_CONST ${ICONV_CONST}) -set(USE_MBSTATE_T 1) -set(BROKEN_WCHAR_H 0) -set(EILSEQ) -set(HAVE_WCHAR_T 1) -configure_file( - ${LIBICONV_SOURCES_DIR}/include/iconv.h.build.in - ${LIBICONV_SOURCES_DIR}/include/iconv.h - ) -unset(HAVE_VISIBILITY) -unset(ICONV_CONST) -unset(USE_MBSTATE_T) -unset(BROKEN_WCHAR_H) -unset(EILSEQ) -unset(HAVE_WCHAR_T) + set(HAVE_VISIBILITY 0) + set(ICONV_CONST ${ICONV_CONST}) + set(USE_MBSTATE_T 1) + set(BROKEN_WCHAR_H 0) + set(EILSEQ) + set(HAVE_WCHAR_T 1) + configure_file( + ${LIBICONV_SOURCES_DIR}/include/iconv.h.build.in + ${LIBICONV_SOURCES_DIR}/include/iconv.h + ) + unset(HAVE_VISIBILITY) + unset(ICONV_CONST) + unset(USE_MBSTATE_T) + unset(BROKEN_WCHAR_H) + unset(EILSEQ) + unset(HAVE_WCHAR_T) + + if (NOT EXISTS ${LIBICONV_SOURCES_DIR}/include/config.h) + # Create an empty "config.h" for libiconv + file(WRITE ${LIBICONV_SOURCES_DIR}/include/config.h "") + endif() + + include_directories( + ${LIBICONV_SOURCES_DIR}/include + ) -# Create an empty "config.h" for libiconv -file(WRITE ${LIBICONV_SOURCES_DIR}/include/config.h "") + set(LIBICONV_SOURCES + ${LIBICONV_SOURCES_DIR}/lib/iconv.c + ${LIBICONV_SOURCES_DIR}/lib/relocatable.c + ${LIBICONV_SOURCES_DIR}/libcharset/lib/localcharset.c + ${LIBICONV_SOURCES_DIR}/libcharset/lib/relocatable.c + ) -include_directories( - ${LIBICONV_SOURCES_DIR}/include - ) + source_group(ThirdParty\\libiconv REGULAR_EXPRESSION ${LIBICONV_SOURCES_DIR}/.*) + + if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_definitions(-DHAVE_WORKING_O_NOFOLLOW=0) + else() + add_definitions(-DHAVE_WORKING_O_NOFOLLOW=1) + endif() -list(APPEND BOOST_SOURCES - ${LIBICONV_SOURCES_DIR}/lib/iconv.c - ${LIBICONV_SOURCES_DIR}/lib/relocatable.c - ${LIBICONV_SOURCES_DIR}/libcharset/lib/localcharset.c - ${LIBICONV_SOURCES_DIR}/libcharset/lib/relocatable.c - ) + else() + CHECK_INCLUDE_FILE_CXX(iconv.h HAVE_ICONV_H) + if (NOT HAVE_ICONV_H) + message(FATAL_ERROR "Please install the libiconv-dev package") + endif() + + # Check whether the support for libiconv is bundled within the + # standard C library + CHECK_FUNCTION_EXISTS(iconv_open HAVE_ICONV_LIB) + if (NOT HAVE_ICONV_LIB) + # No builtin support for libiconv, try and find an external library. + # Open question: Does this make sense on any platform? + CHECK_LIBRARY_EXISTS(iconv iconv_open "" HAVE_ICONV_LIB_2) + if (NOT HAVE_ICONV_LIB_2) + message(FATAL_ERROR "Please install the libiconv-dev package") + else() + link_libraries(iconv) + endif() + endif() + + endif() +endif()
--- a/Resources/CMake/LibJpegConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/LibJpegConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -2,7 +2,7 @@ set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a) DownloadPackage( "3353992aecaee1805ef4109aadd433e7" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jpegsrc.v9a.tar.gz" + "http://www.orthanc-server.com/downloads/third-party/jpegsrc.v9a.tar.gz" "${LIBJPEG_SOURCES_DIR}") include_directories( @@ -81,6 +81,8 @@ ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY ) + source_group(ThirdParty\\libjpeg REGULAR_EXPRESSION ${LIBJPEG_SOURCES_DIR}/.*) + else() include(FindJPEG)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LibP11Configuration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,72 @@ +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBP11) + SET(LIBP11_SOURCES_DIR ${CMAKE_BINARY_DIR}/libp11-0.4.0) + SET(LIBP11_URL "http://www.orthanc-server.com/downloads/third-party/beid/libp11-0.4.0.tar.gz") + SET(LIBP11_MD5 "00b3e41db5be840d822bda12f3ab2ca7") + + if (IS_DIRECTORY "${LIBP11_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) + endif() + + DownloadPackage(${LIBP11_MD5} ${LIBP11_URL} "${LIBP11_SOURCES_DIR}") + + # Apply the patches + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Patches/libp11-0.4.0.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching libp11") + endif() + + # This command MUST be after applying the patch + file(COPY + ${LIBP11_SOURCES_DIR}/src/engine.h + ${LIBP11_SOURCES_DIR}/src/libp11.h + DESTINATION ${AUTOGENERATED_DIR}/libp11) + + set(LIBP11_SOURCES + #${LIBP11_SOURCES_DIR}/src/eng_front.c + ${LIBP11_SOURCES_DIR}/src/eng_back.c + ${LIBP11_SOURCES_DIR}/src/eng_parse.c + ${LIBP11_SOURCES_DIR}/src/libpkcs11.c + ${LIBP11_SOURCES_DIR}/src/p11_attr.c + ${LIBP11_SOURCES_DIR}/src/p11_cert.c + ${LIBP11_SOURCES_DIR}/src/p11_ec.c + ${LIBP11_SOURCES_DIR}/src/p11_err.c + ${LIBP11_SOURCES_DIR}/src/p11_front.c + ${LIBP11_SOURCES_DIR}/src/p11_key.c + ${LIBP11_SOURCES_DIR}/src/p11_load.c + ${LIBP11_SOURCES_DIR}/src/p11_misc.c + ${LIBP11_SOURCES_DIR}/src/p11_rsa.c + ${LIBP11_SOURCES_DIR}/src/p11_slot.c + ) + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + list(APPEND LIBP11_SOURCES + ${LIBP11_SOURCES_DIR}/src/atfork.c + ) + endif() + + source_group(ThirdParty\\libp11 REGULAR_EXPRESSION ${LIBP11_SOURCES_DIR}/.*) + +else() + check_include_file_cxx(libp11.h HAVE_LIBP11_H) + if (NOT HAVE_LIBP11_H) + message(FATAL_ERROR "Please install the libp11-dev package") + endif() + + check_library_exists(p11 PKCS11_login "" HAVE_LIBP11_LIB) + if (NOT HAVE_LIBP11_LIB) + message(FATAL_ERROR "Please install the libp11-dev package") + endif() + + link_libraries(p11) +endif()
--- a/Resources/CMake/LibPngConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/LibPngConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,6 +1,6 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG) SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) - SET(LIBPNG_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz") + SET(LIBPNG_URL "http://www.orthanc-server.com/downloads/third-party/libpng-1.5.12.tar.gz") SET(LIBPNG_MD5 "8ea7f60347a306c5faf70b977fa80e28") DownloadPackage(${LIBPNG_MD5} ${LIBPNG_URL} "${LIBPNG_SOURCES_DIR}") @@ -46,7 +46,7 @@ -DPNG_IMPEXP= ) - source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) + source_group(ThirdParty\\libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) else() include(FindPNG)
--- a/Resources/CMake/LuaConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/LuaConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,15 +1,33 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_LUA) SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5) SET(LUA_MD5 "2e115fe26e435e33b0d5c022e4490567") - SET(LUA_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz") + SET(LUA_URL "http://www.orthanc-server.com/downloads/third-party/lua-5.1.5.tar.gz") DownloadPackage(${LUA_MD5} ${LUA_URL} "${LUA_SOURCES_DIR}") - add_definitions( - #-DLUA_LIB=1 - #-Dluaall_c=1 - #-DLUA_COMPAT_ALL=1 # Compile a generic version of Lua - ) + if (ENABLE_LUA_MODULES) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + # Enable loading of shared libraries (for UNIX-like) + add_definitions(-DLUA_USE_DLOPEN=1) + + # Publish the functions of the Lua engine (that are built within + # the Orthanc binary) as global symbols, so that the external + # shared libraries can call them + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--export-dynamic") + + elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + add_definitions(-DLUA_DL_DLL=1) # Enable loading of shared libraries (for Microsoft Windows) + + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + add_definitions(-LUA_DL_DYLD=1) # Enable loading of shared libraries (for Apple OS X) + + else() + message(FATAL_ERROR "Support your platform here") + endif() + endif() include_directories( ${LUA_SOURCES_DIR}/src
--- a/Resources/CMake/MongooseConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/MongooseConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -11,7 +11,7 @@ # Use Mongoose 3.1 DownloadPackage( "e718fc287b4eb1bd523be3fa00942bb0" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz" + "http://www.orthanc-server.com/downloads/third-party/mongoose-3.1.tgz" "${MONGOOSE_SOURCES_DIR}") add_definitions(-DMONGOOSE_USE_CALLBACKS=0) @@ -21,7 +21,7 @@ # Use Mongoose 3.8 DownloadPackage( "7e3296295072792cdc3c633f9404e0c3" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.8.tgz" + "http://www.orthanc-server.com/downloads/third-party/mongoose-3.8.tgz" "${MONGOOSE_SOURCES_DIR}") add_definitions(-DMONGOOSE_USE_CALLBACKS=1)
--- a/Resources/CMake/OpenSslConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/OpenSslConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,11 +1,7 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL) - # WARNING - We had to repack the upstream ".tar.gz" file to a ZIP - # file, as the upstream distribution ships symbolic links that are - # not always properly handled when uncompressing on Windows. - - SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2d) - SET(OPENSSL_URL "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.2d.zip") - SET(OPENSSL_MD5 "4b2ac15fc6db17f3dadc54482d3eee85") + SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2o) + SET(OPENSSL_URL "http://www.orthanc-server.com/downloads/third-party/openssl-1.0.2o.tar.gz") + SET(OPENSSL_MD5 "44279b8557c3247cbe324e2322ecd114") if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}") set(FirstRun OFF) @@ -15,6 +11,116 @@ 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 @@ -25,9 +131,6 @@ -DOPENSSL_NO_BF -DOPENSSL_NO_CAMELLIA -DOPENSSL_NO_CAST - -DOPENSSL_NO_EC - -DOPENSSL_NO_ECDH - -DOPENSSL_NO_ECDSA -DOPENSSL_NO_EC_NISTP_64_GCC_128 -DOPENSSL_NO_GMP -DOPENSSL_NO_GOST @@ -37,7 +140,7 @@ -DOPENSSL_NO_KRB5 -DOPENSSL_NO_MD2 -DOPENSSL_NO_MDC2 - -DOPENSSL_NO_MD4 + #-DOPENSSL_NO_MD4 # MD4 is necessary for MariaDB/MySQL client -DOPENSSL_NO_RC2 -DOPENSSL_NO_RC4 -DOPENSSL_NO_RC5 @@ -78,6 +181,7 @@ ${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 @@ -99,6 +203,24 @@ ${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() @@ -124,6 +246,9 @@ ${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 @@ -173,26 +298,42 @@ ${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") - elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - execute_process( - COMMAND ${PATCH_EXECUTABLE} -N ui_openssl.c -i ${ORTHANC_ROOT}/Resources/Patches/openssl-lsb.diff - WORKING_DIRECTORY ${OPENSSL_SOURCES_DIR}/crypto/ui - RESULT_VARIABLE Failure - ) - - if (Failure AND FirstRun) - message(FATAL_ERROR "Error while patching a file") + if (ENABLE_OPENSSL_ENGINES) + link_libraries(crypt32) endif() endif() + source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*) + else() include(FindOpenSSL)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,620 @@ +## +## This is a CMake configuration file that configures the core +## libraries of Orthanc. This file can be used by external projects so +## as to gain access to the Orthanc APIs (the most prominent examples +## are currently "Stone of Orthanc" and "Orthanc for whole-slide +## imaging plugin"). +## + + +##################################################################### +## Configuration of the components +##################################################################### + +# Path to the root folder of the Orthanc distribution +set(ORTHANC_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..) + +# Some basic inclusions +include(CMakePushCheckState) +include(CheckFunctionExists) +include(CheckIncludeFile) +include(CheckIncludeFileCXX) +include(CheckIncludeFiles) +include(CheckLibraryExists) +include(CheckStructHasMember) +include(CheckSymbolExists) +include(CheckTypeSize) +include(FindPythonInterp) + +include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake) + + +##################################################################### +## Disable unneeded macros +##################################################################### + +if (NOT ENABLE_SQLITE) + unset(USE_SYSTEM_SQLITE CACHE) + add_definitions(-DORTHANC_ENABLE_SQLITE=0) +endif() + +if (NOT ENABLE_CRYPTO_OPTIONS) + unset(ENABLE_SSL CACHE) + unset(ENABLE_PKCS11 CACHE) + unset(ENABLE_OPENSSL_ENGINES CACHE) + unset(USE_SYSTEM_OPENSSL CACHE) + unset(USE_SYSTEM_LIBP11 CACHE) + add_definitions( + -DORTHANC_ENABLE_SSL=0 + -DORTHANC_ENABLE_PKCS11=0 + ) +endif() + +if (NOT ENABLE_WEB_CLIENT) + unset(USE_SYSTEM_CURL CACHE) + add_definitions(-DORTHANC_ENABLE_CURL=0) +endif() + +if (NOT ENABLE_WEB_SERVER) + unset(ENABLE_CIVETWEB CACHE) + unset(USE_SYSTEM_CIVETWEB CACHE) + unset(USE_SYSTEM_MONGOOSE CACHE) + add_definitions( + -DORTHANC_ENABLE_CIVETWEB=0 + -DORTHANC_ENABLE_MONGOOSE=0 + ) +endif() + +if (NOT ENABLE_JPEG) + unset(USE_SYSTEM_LIBJPEG CACHE) + add_definitions(-DORTHANC_ENABLE_JPEG=0) +endif() + +if (NOT ENABLE_ZLIB) + unset(USE_SYSTEM_ZLIB CACHE) + add_definitions(-DORTHANC_ENABLE_ZLIB=0) +endif() + +if (NOT ENABLE_PNG) + unset(USE_SYSTEM_LIBPNG CACHE) + add_definitions(-DORTHANC_ENABLE_PNG=0) +endif() + +if (NOT ENABLE_LUA) + unset(USE_SYSTEM_LUA CACHE) + unset(ENABLE_LUA_MODULES CACHE) + add_definitions(-DORTHANC_ENABLE_LUA=0) +endif() + +if (NOT ENABLE_PUGIXML) + unset(USE_SYSTEM_PUGIXML CACHE) + add_definitions(-DORTHANC_ENABLE_PUGIXML=0) +endif() + +if (NOT ENABLE_LOCALE) + unset(USE_SYSTEM_LIBICONV CACHE) + add_definitions(-DORTHANC_ENABLE_LOCALE=0) +endif() + +if (NOT ENABLE_GOOGLE_TEST) + unset(USE_SYSTEM_GOOGLE_TEST CACHE) + unset(USE_GOOGLE_TEST_DEBIAN_PACKAGE CACHE) +endif() + +if (NOT ENABLE_DCMTK) + add_definitions( + -DORTHANC_ENABLE_DCMTK=0 + -DORTHANC_ENABLE_DCMTK_NETWORKING=0 + ) + unset(DCMTK_DICTIONARY_DIR CACHE) + unset(USE_DCMTK_360 CACHE) + unset(USE_DCMTK_362_PRIVATE_DIC CACHE) + unset(USE_SYSTEM_DCMTK CACHE) + unset(ENABLE_DCMTK_JPEG CACHE) + unset(ENABLE_DCMTK_JPEG_LOSSLESS CACHE) +endif() + + +##################################################################### +## List of source files +##################################################################### + +set(ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Cache/MemoryCache.cpp + ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/FileStorage/MemoryStorageArea.cpp + ${ORTHANC_ROOT}/Core/Logging.cpp + ${ORTHANC_ROOT}/Core/SerializationToolbox.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp + ) + +if (ENABLE_MODULE_IMAGES) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Images/Font.cpp + ${ORTHANC_ROOT}/Core/Images/FontRegistry.cpp + ${ORTHANC_ROOT}/Core/Images/IImageWriter.cpp + ${ORTHANC_ROOT}/Core/Images/Image.cpp + ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp + ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp + ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp + ${ORTHANC_ROOT}/Core/Images/PamReader.cpp + ${ORTHANC_ROOT}/Core/Images/PamWriter.cpp + ) +endif() + +if (ENABLE_MODULE_DICOM) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomImageInformation.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomInstanceHasher.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomIntegerPixelAccessor.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp + ) +endif() + +if (ENABLE_MODULE_JOBS) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/JobsEngine/GenericJobUnserializer.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobInfo.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobStatus.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobStepResult.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/Operations/JobOperationValues.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/Operations/LogJobOperation.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/SetOfCommandsJob.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/SetOfInstancesJob.cpp + ) +endif() + + + +##################################################################### +## Configuration of optional third-party dependencies +##################################################################### + + +## +## Embedded database: SQLite +## + +if (ENABLE_SQLITE) + include(${CMAKE_CURRENT_LIST_DIR}/SQLiteConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_SQLITE=1) + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/SQLite/Connection.cpp + ${ORTHANC_ROOT}/Core/SQLite/FunctionContext.cpp + ${ORTHANC_ROOT}/Core/SQLite/Statement.cpp + ${ORTHANC_ROOT}/Core/SQLite/StatementId.cpp + ${ORTHANC_ROOT}/Core/SQLite/StatementReference.cpp + ${ORTHANC_ROOT}/Core/SQLite/Transaction.cpp + ) +endif() + + +## +## Cryptography: OpenSSL and libp11 +## Must be above "ENABLE_WEB_CLIENT" and "ENABLE_WEB_SERVER" +## + +if (ENABLE_CRYPTO_OPTIONS) + if (ENABLE_SSL) + include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_SSL=1) + else() + unset(ENABLE_OPENSSL_ENGINES CACHE) + unset(USE_SYSTEM_OPENSSL CACHE) + add_definitions(-DORTHANC_ENABLE_SSL=0) + endif() + + if (ENABLE_PKCS11) + if (ENABLE_SSL) + include(${CMAKE_CURRENT_LIST_DIR}/LibP11Configuration.cmake) + + add_definitions(-DORTHANC_ENABLE_PKCS11=1) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Pkcs11.cpp + ) + else() + message(FATAL_ERROR "OpenSSL is required to enable PKCS#11 support") + endif() + else() + add_definitions(-DORTHANC_ENABLE_PKCS11=0) + endif() +endif() + + +## +## HTTP client: libcurl +## + +if (ENABLE_WEB_CLIENT) + include(${CMAKE_CURRENT_LIST_DIR}/LibCurlConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_CURL=1) + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/HttpClient.cpp + ) +endif() + + +## +## HTTP server: Mongoose 3.8 or Civetweb +## + +if (ENABLE_WEB_SERVER) + if (ENABLE_CIVETWEB) + include(${CMAKE_CURRENT_LIST_DIR}/CivetwebConfiguration.cmake) + add_definitions( + -DORTHANC_ENABLE_CIVETWEB=1 + -DORTHANC_ENABLE_MONGOOSE=0 + ) + else() + include(${CMAKE_CURRENT_LIST_DIR}/MongooseConfiguration.cmake) + add_definitions( + -DORTHANC_ENABLE_CIVETWEB=0 + -DORTHANC_ENABLE_MONGOOSE=1 + ) + endif() + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/HttpServer/BufferHttpSender.cpp + ${ORTHANC_ROOT}/Core/HttpServer/FilesystemHttpHandler.cpp + ${ORTHANC_ROOT}/Core/HttpServer/FilesystemHttpSender.cpp + ${ORTHANC_ROOT}/Core/HttpServer/HttpContentNegociation.cpp + ${ORTHANC_ROOT}/Core/HttpServer/HttpFileSender.cpp + ${ORTHANC_ROOT}/Core/HttpServer/HttpOutput.cpp + ${ORTHANC_ROOT}/Core/HttpServer/HttpStreamTranscoder.cpp + ${ORTHANC_ROOT}/Core/HttpServer/HttpToolbox.cpp + ${ORTHANC_ROOT}/Core/HttpServer/MongooseServer.cpp + ${ORTHANC_ROOT}/Core/HttpServer/StringHttpOutput.cpp + ${ORTHANC_ROOT}/Core/RestApi/RestApi.cpp + ${ORTHANC_ROOT}/Core/RestApi/RestApiCall.cpp + ${ORTHANC_ROOT}/Core/RestApi/RestApiGetCall.cpp + ${ORTHANC_ROOT}/Core/RestApi/RestApiHierarchy.cpp + ${ORTHANC_ROOT}/Core/RestApi/RestApiOutput.cpp + ${ORTHANC_ROOT}/Core/RestApi/RestApiPath.cpp + ) +endif() + + +## +## JPEG support: libjpeg +## + +if (ENABLE_JPEG) + if (NOT ENABLE_MODULE_IMAGES) + message(FATAL_ERROR "Image processing primitives must be enabled if enabling libjpeg support") + endif() + + include(${CMAKE_CURRENT_LIST_DIR}/LibJpegConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_JPEG=1) + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp + ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp + ${ORTHANC_ROOT}/Core/Images/JpegWriter.cpp + ) +endif() + + +## +## zlib support +## + +if (ENABLE_ZLIB) + include(${CMAKE_CURRENT_LIST_DIR}/ZlibConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_ZLIB=1) + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Compression/DeflateBaseCompressor.cpp + ${ORTHANC_ROOT}/Core/Compression/GzipCompressor.cpp + ${ORTHANC_ROOT}/Core/Compression/ZlibCompressor.cpp + ) + + if (NOT ORTHANC_SANDBOXED) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Compression/HierarchicalZipWriter.cpp + ${ORTHANC_ROOT}/Core/Compression/ZipWriter.cpp + ${ORTHANC_ROOT}/Core/FileStorage/StorageAccessor.cpp + ) + endif() +endif() + + +## +## PNG support: libpng (in conjunction with zlib) +## + +if (ENABLE_PNG) + if (NOT ENABLE_ZLIB) + message(FATAL_ERROR "Support for zlib must be enabled if enabling libpng support") + endif() + + if (NOT ENABLE_MODULE_IMAGES) + message(FATAL_ERROR "Image processing primitives must be enabled if enabling libpng support") + endif() + + include(${CMAKE_CURRENT_LIST_DIR}/LibPngConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_PNG=1) + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Images/PngReader.cpp + ${ORTHANC_ROOT}/Core/Images/PngWriter.cpp + ) +endif() + + +## +## Lua support +## + +if (ENABLE_LUA) + include(${CMAKE_CURRENT_LIST_DIR}/LuaConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_LUA=1) + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Lua/LuaContext.cpp + ${ORTHANC_ROOT}/Core/Lua/LuaFunctionCall.cpp + ) +endif() + + +## +## XML support: pugixml +## + +if (ENABLE_PUGIXML) + include(${CMAKE_CURRENT_LIST_DIR}/PugixmlConfiguration.cmake) + add_definitions(-DORTHANC_ENABLE_PUGIXML=1) +endif() + + +## +## Locale support: libiconv +## + +if (ENABLE_LOCALE) + if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + # In WebAssembly or asm.js, we rely on the version of iconv that + # is shipped with the stdlib + unset(USE_BOOST_ICONV CACHE) + else() + include(${CMAKE_CURRENT_LIST_DIR}/LibIconvConfiguration.cmake) + endif() + + add_definitions(-DORTHANC_ENABLE_LOCALE=1) +endif() + + +## +## Google Test for unit testing +## + +if (ENABLE_GOOGLE_TEST) + include(${CMAKE_CURRENT_LIST_DIR}/GoogleTestConfiguration.cmake) +endif() + + + +##################################################################### +## Inclusion of mandatory third-party dependencies +##################################################################### + +include(${CMAKE_CURRENT_LIST_DIR}/JsonCppConfiguration.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/UuidConfiguration.cmake) + +# We put Boost as the last dependency, as it is the heaviest to +# configure, which allows to quickly spot problems when configuring +# static builds in other dependencies +include(${CMAKE_CURRENT_LIST_DIR}/BoostConfiguration.cmake) + + +##################################################################### +## Optional configuration of DCMTK +##################################################################### + +if (ENABLE_DCMTK) + if (NOT ENABLE_LOCALE) + message(FATAL_ERROR "Support for locales must be enabled if enabling DCMTK support") + endif() + + if (NOT ENABLE_MODULE_DICOM) + message(FATAL_ERROR "DICOM module must be enabled if enabling DCMTK support") + endif() + + include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfiguration.cmake) + + add_definitions(-DORTHANC_ENABLE_DCMTK=1) + + if (ENABLE_DCMTK_JPEG) + add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG=1) + else() + add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG=0) + endif() + + if (ENABLE_DCMTK_JPEG_LOSSLESS) + add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=1) + else() + add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=0) + endif() + + set(ORTHANC_DICOM_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/DicomParsing/DicomModification.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/FromDcmtkBridge.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomFile.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/ToDcmtkBridge.cpp + + ${ORTHANC_ROOT}/Core/DicomParsing/Internals/DicomFrameIndex.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/Internals/DicomImageDecoder.cpp + ) + + if (NOT ORTHANC_SANDBOXED) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/DicomParsing/DicomDirWriter.cpp + ) + endif() + + if (ENABLE_DCMTK_NETWORKING) + add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=1) + list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/DicomNetworking/DicomFindAnswers.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/DicomServer.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/DicomUserConnection.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/CommandDispatcher.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/FindScp.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/MoveScp.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/StoreScp.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/RemoteModalityParameters.cpp + ${ORTHANC_ROOT}/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp + ) + else() + add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=0) + endif() + + if (STANDALONE_BUILD AND NOT HAS_EMBEDDED_RESOURCES) + EmbedResources( + ${DCMTK_DICTIONARIES} + ) + list(APPEND ORTHANC_DICOM_SOURCES_DEPENDENCIES + ${AUTOGENERATED_SOURCES} + ) + endif() +endif() + + +##################################################################### +## Configuration of the C/C++ macros +##################################################################### + +add_definitions( + -DORTHANC_API_VERSION="${ORTHANC_API_VERSION}" + -DORTHANC_DATABASE_VERSION=${ORTHANC_DATABASE_VERSION} + -DORTHANC_DEFAULT_DICOM_ENCODING=Encoding_Latin1 + -DORTHANC_ENABLE_BASE64=1 + -DORTHANC_ENABLE_MD5=1 + -DORTHANC_MAXIMUM_TAG_LENGTH=256 + -DORTHANC_VERSION="${ORTHANC_VERSION}" + ) + + +if (ORTHANC_SANDBOXED) + add_definitions( + -DORTHANC_SANDBOXED=1 + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + + if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + add_definitions( + -DORTHANC_ENABLE_LOGGING=1 + -DORTHANC_ENABLE_LOGGING_STDIO=1 + ) + else() + add_definitions( + -DORTHANC_ENABLE_LOGGING=0 + ) + endif() + +else() + add_definitions( + -DORTHANC_ENABLE_LOGGING=1 + -DORTHANC_ENABLE_LOGGING_STDIO=0 + -DORTHANC_SANDBOXED=0 + ) + + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/Cache/SharedArchive.cpp + ${ORTHANC_ROOT}/Core/FileStorage/FilesystemStorage.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/RunnableWorkersPool.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/Semaphore.cpp + ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp + ${ORTHANC_ROOT}/Core/SharedLibrary.cpp + ${ORTHANC_ROOT}/Core/SystemToolbox.cpp + ${ORTHANC_ROOT}/Core/TemporaryFile.cpp + ) + + if (ENABLE_MODULE_JOBS) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/JobsEngine/JobsEngine.cpp + ${ORTHANC_ROOT}/Core/JobsEngine/JobsRegistry.cpp + ) + endif() +endif() + + +if (HAS_EMBEDDED_RESOURCES) + add_definitions(-DORTHANC_HAS_EMBEDDED_RESOURCES=1) + + if (ENABLE_WEB_SERVER) + list(APPEND ORTHANC_CORE_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/HttpServer/EmbeddedResourceHttpHandler.cpp + ) + endif() +else() + add_definitions(-DORTHANC_HAS_EMBEDDED_RESOURCES=0) +endif() + + +##################################################################### +## Gathering of all the source code +##################################################################### + +# The "xxx_INTERNAL" variables list the source code that belongs to +# the Orthanc project. It can be used to configure precompiled headers +# if using Microsoft Visual Studio. + +# The "xxx_DEPENDENCIES" variables list the source code coming from +# third-party dependencies. + + +set(ORTHANC_CORE_SOURCES_DEPENDENCIES + ${BOOST_SOURCES} + ${CIVETWEB_SOURCES} + ${CURL_SOURCES} + ${JSONCPP_SOURCES} + ${LIBICONV_SOURCES} + ${LIBJPEG_SOURCES} + ${LIBP11_SOURCES} + ${LIBPNG_SOURCES} + ${LUA_SOURCES} + ${MONGOOSE_SOURCES} + ${OPENSSL_SOURCES} + ${PUGIXML_SOURCES} + ${SQLITE_SOURCES} + ${UUID_SOURCES} + ${ZLIB_SOURCES} + + ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c + ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp + ) + +if (ENABLE_ZLIB AND NOT ORTHANC_SANDBOXED) + list(APPEND ORTHANC_CORE_SOURCES_DEPENDENCIES + # This is the minizip distribution to create ZIP files using zlib + ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c + ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/zip.c + ) +endif() + + +set(ORTHANC_CORE_SOURCES + ${ORTHANC_CORE_SOURCES_INTERNAL} + ${ORTHANC_CORE_SOURCES_DEPENDENCIES} + ) + +if (ENABLE_DCMTK) + list(APPEND ORTHANC_DICOM_SOURCES_DEPENDENCIES + ${DCMTK_SOURCES} + ) + + set(ORTHANC_DICOM_SOURCES + ${ORTHANC_DICOM_SOURCES_INTERNAL} + ${ORTHANC_DICOM_SOURCES_DEPENDENCIES} + ) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/OrthancFrameworkParameters.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,118 @@ +##################################################################### +## Versioning information +##################################################################### + +# Version of the build, should always be "mainline" except in release branches +set(ORTHANC_VERSION "mainline") + +# Version of the database schema. History: +# * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning +# * Orthanc 0.3.1 = version 2 +# * Orthanc 0.4.0 -> Orthanc 0.7.2 = version 3 +# * Orthanc 0.7.3 -> Orthanc 0.8.4 = version 4 +# * Orthanc 0.8.5 -> Orthanc 0.9.4 = version 5 +# * Orthanc 0.9.5 -> mainline = version 6 +set(ORTHANC_DATABASE_VERSION 6) + +# 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 "1.2") + + +##################################################################### +## CMake parameters tunable by the user +##################################################################### + +# Support of static compilation +set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") +set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +set(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") + +# Generic parameters of the build +set(ENABLE_CIVETWEB OFF CACHE BOOL "Use Civetweb instead of Mongoose (experimental)") +set(ENABLE_PKCS11 OFF CACHE BOOL "Enable PKCS#11 for HTTPS client authentication using hardware security modules and smart cards") +set(ENABLE_PROFILING OFF CACHE BOOL "Whether to enable the generation of profiling information with gprof") +set(ENABLE_SSL ON CACHE BOOL "Include support for SSL") +set(ENABLE_LUA_MODULES OFF CACHE BOOL "Enable support for loading external Lua modules (only meaningful if using static version of the Lua engine)") + +# Parameters to fine-tune linking against system libraries +set(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +set(USE_SYSTEM_CIVETWEB ON CACHE BOOL "Use the system version of Civetweb (experimental)") +set(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl") +set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") +set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +set(USE_SYSTEM_LIBICONV ON CACHE BOOL "Use the system version of libiconv") +set(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg") +set(USE_SYSTEM_LIBP11 OFF CACHE BOOL "Use the system version of libp11 (PKCS#11 wrapper library)") +set(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng") +set(USE_SYSTEM_LUA ON CACHE BOOL "Use the system version of Lua") +set(USE_SYSTEM_MONGOOSE ON CACHE BOOL "Use the system version of Mongoose") +set(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL") +set(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml") +set(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite") +set(USE_SYSTEM_UUID ON CACHE BOOL "Use the system version of the uuid library from e2fsprogs") +set(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib") + +# 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(USE_DCMTK_360 OFF CACHE BOOL "Use older DCMTK version 3.6.0 in static builds (instead of default 3.6.2)") +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_JPEG ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression") +set(ENABLE_DCMTK_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression") + +# Advanced and distribution-specific parameters +set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") +set(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)") +set(USE_BOOST_ICONV ON CACHE BOOL "Use iconv instead of wconv (Windows only)") +set(USE_PUGIXML ON CACHE BOOL "Use the Pugixml parser (turn off only for debug)") +set(USE_LEGACY_JSONCPP OFF CACHE BOOL "Use the old branch 0.x.y of JsonCpp, that does not require a C++11 compiler (for old versions of Visual Studio)") + +mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) +mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS) +mark_as_advanced(USE_BOOST_ICONV) +mark_as_advanced(USE_PUGIXML) +mark_as_advanced(USE_LEGACY_JSONCPP) + + +##################################################################### +## Internal CMake parameters to enable the optional subcomponents of +## the Orthanc framework +##################################################################### + +# These options must be set to "ON" if compiling Orthanc, but might be +# set to "OFF" by third-party projects if their associated features +# are not required + +set(ENABLE_CRYPTO_OPTIONS OFF CACHE INTERNAL "Show options related to cryptography") +set(ENABLE_JPEG OFF CACHE INTERNAL "Enable support of JPEG") +set(ENABLE_GOOGLE_TEST OFF CACHE INTERNAL "Enable support of Google Test") +set(ENABLE_LOCALE OFF CACHE INTERNAL "Enable support for locales (notably in Boost)") +set(ENABLE_LUA OFF CACHE INTERNAL "Enable support of Lua scripting") +set(ENABLE_PNG OFF CACHE INTERNAL "Enable support of PNG") +set(ENABLE_PUGIXML OFF CACHE INTERNAL "Enable support of XML through Pugixml") +set(ENABLE_SQLITE OFF CACHE INTERNAL "Enable support of SQLite databases") +set(ENABLE_ZLIB OFF CACHE INTERNAL "Enable support of zlib") +set(ENABLE_WEB_CLIENT OFF CACHE INTERNAL "Enable Web client") +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_OPENSSL_ENGINES OFF CACHE INTERNAL "Enable support of engines in OpenSSL") + +set(HAS_EMBEDDED_RESOURCES OFF CACHE INTERNAL + "Whether resources are auto-generated using EmbedResources.py") + +set(ORTHANC_SANDBOXED OFF CACHE INTERNAL + "Whether Orthanc runs inside a sandboxed environment (such as Google NaCl or WebAssembly)") + + +# +# These options can be used to turn off some modules of the Orthanc +# framework, in order to speed up the compilation time of third-party +# projects. +# + +set(ENABLE_MODULE_IMAGES ON CACHE INTERNAL "Enable module for image processing") +set(ENABLE_MODULE_JOBS ON CACHE INTERNAL "Enable module for jobs") +set(ENABLE_MODULE_DICOM ON CACHE INTERNAL "Enable module for DICOM handling")
--- a/Resources/CMake/PugixmlConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/PugixmlConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,31 +1,26 @@ -if (USE_PUGIXML) - add_definitions(-DORTHANC_PUGIXML_ENABLED=1) +if (STATIC_BUILD OR NOT USE_SYSTEM_PUGIXML) + set(PUGIXML_SOURCES_DIR ${CMAKE_BINARY_DIR}/pugixml-1.4) + set(PUGIXML_MD5 "7c56c91cfe3ecdee248a8e4892ef5781") + set(PUGIXML_URL "http://www.orthanc-server.com/downloads/third-party/pugixml-1.4.tar.gz") - if (STATIC_BUILD OR NOT USE_SYSTEM_PUGIXML) - set(PUGIXML_SOURCES_DIR ${CMAKE_BINARY_DIR}/pugixml-1.4) - set(PUGIXML_MD5 "7c56c91cfe3ecdee248a8e4892ef5781") - set(PUGIXML_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/pugixml-1.4.tar.gz") + DownloadPackage(${PUGIXML_MD5} ${PUGIXML_URL} "${PUGIXML_SOURCES_DIR}") - DownloadPackage(${PUGIXML_MD5} ${PUGIXML_URL} "${PUGIXML_SOURCES_DIR}") + include_directories( + ${PUGIXML_SOURCES_DIR}/src + ) - include_directories( - ${PUGIXML_SOURCES_DIR}/src - ) + set(PUGIXML_SOURCES + #${PUGIXML_SOURCES_DIR}/src/vlog_is_on.cc + ${PUGIXML_SOURCES_DIR}/src/pugixml.cpp + ) - set(PUGIXML_SOURCES - #${PUGIXML_SOURCES_DIR}/src/vlog_is_on.cc - ${PUGIXML_SOURCES_DIR}/src/pugixml.cpp - ) + source_group(ThirdParty\\pugixml REGULAR_EXPRESSION ${PUGIXML_SOURCES_DIR}/.*) - else() - CHECK_INCLUDE_FILE_CXX(pugixml.hpp HAVE_PUGIXML_H) - if (NOT HAVE_PUGIXML_H) - message(FATAL_ERROR "Please install the libpugixml-dev package") - endif() - - link_libraries(pugixml) +else() + CHECK_INCLUDE_FILE_CXX(pugixml.hpp HAVE_PUGIXML_H) + if (NOT HAVE_PUGIXML_H) + message(FATAL_ERROR "Please install the libpugixml-dev package") endif() -else() - add_definitions(-DORTHANC_PUGIXML_ENABLED=0) + link_libraries(pugixml) endif()
--- a/Resources/CMake/SQLiteConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/SQLiteConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -15,9 +15,11 @@ if (SQLITE_STATIC) - SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300) - SET(SQLITE_MD5 "5fbeff9645ab035a1f580e90b279a16d") - SET(SQLITE_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip") + SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3210000) + SET(SQLITE_MD5 "fe330e88d81e77e1e61554a370ae5001") + SET(SQLITE_URL "http://www.orthanc-server.com/downloads/third-party/sqlite-amalgamation-3210000.zip") + + add_definitions(-DORTHANC_SQLITE_VERSION=3021000) DownloadPackage(${SQLITE_MD5} ${SQLITE_URL} "${SQLITE_SOURCES_DIR}") @@ -52,7 +54,10 @@ # Autodetection of the version of SQLite file(STRINGS "${SQLITE_INCLUDE_DIR}/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") - string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1}) + string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER2 ${SQLITE_VERSION_NUMBER1}) + + # Remove the trailing spaces to convert the string to a proper integer + string(STRIP ${SQLITE_VERSION_NUMBER2} SQLITE_VERSION_NUMBER) message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}") @@ -61,5 +66,7 @@ message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_SYSTEM_SQLITE to OFF.") ENDIF() + add_definitions(-DORTHANC_SQLITE_VERSION=${SQLITE_VERSION_NUMBER}) + link_libraries(sqlite3) endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/UuidConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,102 @@ +if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + + if (STATIC_BUILD OR NOT USE_SYSTEM_UUID) + SET(E2FSPROGS_SOURCES_DIR ${CMAKE_BINARY_DIR}/e2fsprogs-1.43.8) + SET(E2FSPROGS_URL "http://www.orthanc-server.com/downloads/third-party/e2fsprogs-1.43.8.tar.gz") + SET(E2FSPROGS_MD5 "670b7a74a8ead5333acf21b9afc92b3c") + + DownloadPackage(${E2FSPROGS_MD5} ${E2FSPROGS_URL} "${E2FSPROGS_SOURCES_DIR}") + + include_directories( + ${E2FSPROGS_SOURCES_DIR}/lib + ) + + set(UUID_SOURCES + #${E2FSPROGS_SOURCES_DIR}/lib/uuid/tst_uuid.c + #${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid_time.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/clear.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/compare.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/copy.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/gen_uuid.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/isnull.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/pack.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/parse.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/unpack.c + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/unparse.c + ) + + check_include_file("net/if.h" HAVE_NET_IF_H) + check_include_file("net/if_dl.h" HAVE_NET_IF_DL_H) + check_include_file("netinet/in.h" HAVE_NETINET_IN_H) + check_include_file("stdlib.h" HAVE_STDLIB_H) + check_include_file("sys/file.h" HAVE_SYS_FILE_H) + check_include_file("sys/ioctl.h" HAVE_SYS_IOCTL_H) + check_include_file("sys/resource.h" HAVE_SYS_RESOURCE_H) + check_include_file("sys/socket.h" HAVE_SYS_SOCKET_H) + check_include_file("sys/sockio.h" HAVE_SYS_SOCKIO_H) + check_include_file("sys/syscall.h" HAVE_SYS_SYSCALL_H) + check_include_file("sys/time.h" HAVE_SYS_TIME_H) + check_include_file("sys/un.h" HAVE_SYS_UN_H) + check_include_file("unistd.h" HAVE_UNISTD_H) + + if (NOT HAVE_NET_IF_H) # This is the case of OpenBSD + unset(HAVE_NET_IF_H CACHE) + check_include_files("sys/socket.h;net/if.h" HAVE_NET_IF_H) + endif() + + if (NOT HAVE_NETINET_TCP_H) # This is the case of OpenBSD + unset(HAVE_NETINET_TCP_H CACHE) + check_include_files("sys/socket.h;netinet/tcp.h" HAVE_NETINET_TCP_H) + endif() + + if (NOT EXISTS ${E2FSPROGS_SOURCES_DIR}/lib/uuid/config.h) + file(WRITE ${E2FSPROGS_SOURCES_DIR}/lib/uuid/config.h.cmake " +#cmakedefine HAVE_NET_IF_H \@HAVE_NET_IF_H\@ +#cmakedefine HAVE_NET_IF_DL_H \@HAVE_NET_IF_DL_H\@ +#cmakedefine HAVE_NETINET_IN_H \@HAVE_NETINET_IN_H\@ +#cmakedefine HAVE_STDLIB_H \@HAVE_STDLIB_H \@ +#cmakedefine HAVE_SYS_FILE_H \@HAVE_SYS_FILE_H\@ +#cmakedefine HAVE_SYS_IOCTL_H \@HAVE_SYS_IOCTL_H\@ +#cmakedefine HAVE_SYS_RESOURCE_H \@HAVE_SYS_RESOURCE_H\@ +#cmakedefine HAVE_SYS_SOCKET_H \@HAVE_SYS_SOCKET_H\@ +#cmakedefine HAVE_SYS_SOCKIO_H \@HAVE_SYS_SOCKIO_H\@ +#cmakedefine HAVE_SYS_SYSCALL_H \@HAVE_SYS_SYSCALL_H\@ +#cmakedefine HAVE_SYS_TIME_H \@HAVE_SYS_TIME_H\@ +#cmakedefine HAVE_SYS_UN_H \@HAVE_SYS_UN_H\@ +#cmakedefine HAVE_UNISTD_H \@HAVE_UNISTD_H\@ +") + endif() + + configure_file( + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/config.h.cmake + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/config.h + ) + + configure_file( + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid.h.in + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid.h + ) + + if (NOT EXISTS ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid_types.h) + file(WRITE + ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid_types.h + "#include <stdint.h>\n") + endif() + + source_group(ThirdParty\\uuid REGULAR_EXPRESSION ${E2FSPROGS_SOURCES_DIR}/.*) + + else() + CHECK_INCLUDE_FILE(uuid/uuid.h HAVE_UUID_H) + if (NOT HAVE_UUID_H) + message(FATAL_ERROR "Please install uuid-dev, e2fsprogs (OpenBSD) or e2fsprogs-libuuid (FreeBSD)") + endif() + + check_library_exists(uuid uuid_generate_random "" HAVE_UUID_LIB) + if (NOT HAVE_UUID_LIB) + message(FATAL_ERROR "Unable to find the uuid library") + endif() + + link_libraries(uuid) + endif() + +endif()
--- a/Resources/CMake/VisualStudioPrecompiledHeaders.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/VisualStudioPrecompiledHeaders.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,4 +1,4 @@ -macro(ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS PrecompiledHeaders PrecompiledSource Sources) +macro(ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS PrecompiledHeaders PrecompiledSource Sources Target) get_filename_component(PrecompiledBasename ${PrecompiledHeaders} NAME_WE) set(PrecompiledBinary "${PrecompiledBasename}_$(ConfigurationName).pch") @@ -10,5 +10,5 @@ PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledHeaders}\" /FI\"${PrecompiledHeaders}\" /Fp\"${PrecompiledBinary}\"" OBJECT_DEPENDS "${PrecompiledBinary}") - list(APPEND ${Sources} ${PrecompiledSource}) + set(${Target} ${PrecompiledSource}) endmacro()
--- a/Resources/CMake/ZlibConfiguration.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/CMake/ZlibConfiguration.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,7 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB) - SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7) - SET(ZLIB_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz") - SET(ZLIB_MD5 "60df6a37c56e7c1366cca812414f7b85") + SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.11) + SET(ZLIB_URL "http://www.orthanc-server.com/downloads/third-party/zlib-1.2.11.tar.gz") + SET(ZLIB_MD5 "1c9f62f0778697a09d36121ead88e08e") DownloadPackage(${ZLIB_MD5} ${ZLIB_URL} "${ZLIB_SOURCES_DIR}") @@ -27,10 +27,19 @@ ${ZLIB_SOURCES_DIR}/zutil.c ) + source_group(ThirdParty\\zlib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*) + + if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + # "ioapi.c" from zlib (minizip) expects the "IOAPI_NO_64" macro to be set to "true" + # https://ohse.de/uwe/articles/lfs.html + add_definitions( + -DIOAPI_NO_64=1 + ) + endif() + else() include(FindZLIB) include_directories(${ZLIB_INCLUDE_DIRS}) link_libraries(${ZLIB_LIBRARIES}) endif() - -source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
--- a/Resources/Configuration.json Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Configuration.json Fri Oct 12 15:18:10 2018 +0200 @@ -7,13 +7,14 @@ // displayed in Orthanc Explorer and at the URI "/system". "Name" : "MyOrthanc", - // Path to the directory that holds the heavyweight files - // (i.e. the raw DICOM instances) + // Path to the directory that holds the heavyweight files (i.e. the + // raw DICOM instances). Backslashes must be either escaped by + // doubling them, or replaced by forward slashes "/". "StorageDirectory" : "OrthancStorage", - // Path to the directory that holds the SQLite index (if unset, - // the value of StorageDirectory is used). This index could be - // stored on a RAM-drive or a SSD device for performance reasons. + // Path to the directory that holds the SQLite index (if unset, the + // value of StorageDirectory is used). This index could be stored on + // a RAM-drive or a SSD device for performance reasons. "IndexDirectory" : "OrthancStorage", // Enable the transparent compression of the DICOM instances @@ -37,10 +38,16 @@ // instance of Orthanc (e.g. "./libPluginTest.so" for Linux, or // "./PluginTest.dll" for Windows). These paths can refer to // folders, in which case they will be scanned non-recursively to - // find shared libraries. + // find shared libraries. Backslashes must be either escaped by + // doubling them, or replaced by forward slashes "/". "Plugins" : [ ], + // Maximum number of processing jobs that are simultaneously running + // at any given time. A value of "0" indicates to use all the + // available CPU logical cores. To emulate Orthanc <= 1.3.2, set + // this value to "1". + "ConcurrentJobs" : 2, /** @@ -80,17 +87,19 @@ // The DICOM Application Entity Title "DicomAet" : "ORTHANC", - // Check whether the called AET corresponds during a DICOM request + // Check whether the called AET corresponds to the AET of Orthanc + // during an incoming DICOM SCU request "DicomCheckCalledAet" : false, // The DICOM port "DicomPort" : 4242, // The default encoding that is assumed for DICOM files without - // "SpecificCharacterSet" DICOM tag. The allowed values are "Ascii", - // "Utf8", "Latin1", "Latin2", "Latin3", "Latin4", "Latin5", - // "Cyrillic", "Windows1251", "Arabic", "Greek", "Hebrew", "Thai", - // "Japanese", and "Chinese". + // "SpecificCharacterSet" DICOM tag, and that is used when answering + // C-Find requests (including worklists). The allowed values are + // "Ascii", "Utf8", "Latin1", "Latin2", "Latin3", "Latin4", + // "Latin5", "Cyrillic", "Windows1251", "Arabic", "Greek", "Hebrew", + // "Thai", "Japanese", and "Chinese". "DefaultEncoding" : "Latin1", // The transfer syntaxes that are accepted by Orthanc C-Store SCP @@ -102,6 +111,15 @@ "Mpeg2TransferSyntaxAccepted" : true, "RleTransferSyntaxAccepted" : true, + // Whether Orthanc accepts to act as C-Store SCP for unknown storage + // SOP classes (aka. "promiscuous mode") + "UnknownSopClassAccepted" : false, + + // Set the timeout (in seconds) after which the DICOM associations + // are closed by the Orthanc SCP (server) if no further DIMSE + // command is received from the SCU (client). + "DicomScpTimeout" : 30, + /** @@ -114,7 +132,8 @@ // Whether or not SSL is enabled "SslEnabled" : false, - // Path to the SSL certificate (meaningful only if SSL is enabled) + // Path to the SSL certificate in the PEM format (meaningful only if + // SSL is enabled) "SslCertificate" : "certificate.pem", // Whether or not the password protection is enabled @@ -140,18 +159,62 @@ * store (shipped in the DCMTK distribution) started by the * command line "storescp 2000". **/ - // "sample" : [ "STORESCP", "localhost", 2000 ] + // "sample" : [ "STORESCP", "127.0.0.1", 2000 ] + + /** + * A fourth parameter is available to enable patches for + * specific PACS manufacturers. The allowed values are currently: + * - "Generic" (default value), + * - "GenericNoWildcardInDates" (to replace "*" by "" in date fields + * in outgoing C-Find requests originating from Orthanc) + * - "GenericNoUniversalWildcard" (to replace "*" by "" in all fields + * in outgoing C-Find SCU requests originating from Orthanc) + * - "StoreScp" (storescp tool from DCMTK), + * - "ClearCanvas", "Dcm4Chee" and "Vitrea". + * This parameter is case-sensitive. + **/ + // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] /** - * A fourth parameter is available to enable patches for a - * specific PACS manufacturer. The allowed values are currently - * "Generic" (default value), "StoreScp" (storescp tool from - * DCMTK), "ClearCanvas", "MedInria", "Dcm4Chee" and - * "SyngoVia". This parameter is case-sensitive. + * By default, the Orthanc SCP accepts all DICOM commands (C-GET, + * C-STORE, C-FIND, C-MOVE) issued by the remote SCU + * modalities. Starting with Orthanc 1.4.3, it is possible to + * specify which DICOM commands are allowed, separately for each + * remote modality, using the syntax below. **/ - // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] + //"untrusted" : { + // "AET" : "ORTHANC", + // "Port" : 104, + // "Host" : "127.0.0.1", + // "AllowEcho" : false, + // "AllowFind" : false, + // "AllowMove" : false, + // "AllowStore" : true + //} }, + // Whether the Orthanc SCP allows incoming C-Echo requests, even + // from SCU modalities it does not know about (i.e. that are not + // listed in the "DicomModalities" option above). Orthanc 1.3.0 + // is the only version to behave as if this argument was set to "false". + "DicomAlwaysAllowEcho" : true, + + // Whether the Orthanc SCP allows incoming C-Store requests, even + // from SCU modalities it does not know about (i.e. that are not + // listed in the "DicomModalities" option above) + "DicomAlwaysAllowStore" : true, + + // Whether Orthanc checks the IP/hostname address of the remote + // modality initiating a DICOM connection (as listed in the + // "DicomModalities" option above). If this option is set to + // "false", Orthanc only checks the AET of the remote modality. + "DicomCheckModalityHost" : false, + + // The timeout (in seconds) after which the DICOM associations are + // considered as closed by the Orthanc SCU (client) if the remote + // DICOM SCP (server) does not answer. + "DicomScuTimeout" : 10, + // The list of the known Orthanc peers "OrthancPeers" : { /** @@ -159,8 +222,25 @@ * followed by the username/password pair (if the password * protection is enabled on the peer). **/ - // "peer" : [ "http://localhost:8043/", "alice", "alicePassword" ] - // "peer2" : [ "http://localhost:8044/" ] + // "peer" : [ "http://127.0.0.1:8043/", "alice", "alicePassword" ] + // "peer2" : [ "http://127.0.0.1:8044/" ] + + /** + * This is another, more advanced format to define Orthanc + * peers. It notably allows to specify HTTP headers, a HTTPS + * client certificate in the PEM format (as in the "--cert" option + * of curl), or to enable PKCS#11 authentication for smart cards. + **/ + // "peer" : { + // "Url" : "http://127.0.0.1:8043/", + // "Username" : "alice", + // "Password" : "alicePassword", + // "HttpHeaders" : { "Token" : "Hello world" }, + // "CertificateFile" : "client.crt", + // "CertificateKeyFile" : "client.key", + // "CertificateKeyPassword" : "certpass", + // "Pkcs11" : false + // } }, // Parameters of the HTTP proxy to be used by Orthanc. If set to the @@ -169,10 +249,18 @@ // "HttpProxy" : "proxyUser:proxyPassword@192.168.0.1:3128" "HttpProxy" : "", + // If set to "true", debug messages from libcurl will be issued + // whenever Orthanc makes an outgoing HTTP request. This is notably + // useful to debug HTTPS-related problems. + "HttpVerbose" : false, + // Set the timeout for HTTP requests issued by Orthanc (in seconds). "HttpTimeout" : 10, - // Enable the verification of the peers during HTTPS requests. + // Enable the verification of the peers during HTTPS requests. This + // option must be set to "false" if using self-signed certificates. + // Pay attention that setting this option to "false" results in + // security risks! // Reference: http://curl.haxx.se/docs/sslcerts.html "HttpsVerifyPeers" : true, @@ -190,17 +278,20 @@ **/ // Dictionary of symbolic names for the user-defined metadata. Each - // entry must map a number between 1024 and 65535 to an unique - // string. + // entry must map an unique string to an unique number between 1024 + // and 65535. Reserved values: + // - The Orthanc whole-slide imaging plugin uses metadata 4200 "UserMetadata" : { // "Sample" : 1024 }, // Dictionary of symbolic names for the user-defined types of - // attached files. Each entry must map a number between 1024 and - // 65535 to an unique string. + // attached files. Each entry must map an unique string to an unique + // number between 1024 and 65535. Optionally, a second argument can + // provided to specify a MIME content type for the attachment. "UserContentType" : { // "sample" : 1024 + // "sample2" : [ 1025, "application/pdf" ] }, // Number of seconds without receiving any instance before a @@ -232,11 +323,13 @@ // some job finishes. "LimitJobs" : 10, - // If this option is set to "false", Orthanc will not log the - // resources that are exported to other DICOM modalities of Orthanc - // peers in the URI "/exports". This is useful to prevent the index - // to grow indefinitely in auto-routing tasks. - "LogExportedResources" : true, + // If this option is set to "true" (default behavior until Orthanc + // 1.3.2), Orthanc will log the resources that are exported to other + // DICOM modalities or Orthanc peers, inside the URI + // "/exports". Setting this option to "false" is useful to prevent + // the index to grow indefinitely in auto-routing tasks (this is the + // default behavior since Orthanc 1.4.0). + "LogExportedResources" : false, // Enable or disable HTTP Keep-Alive (deprecated). Set this option // to "true" only in the case of high HTTP loads. @@ -247,10 +340,11 @@ // this option might prevent the upgrade to newer versions of Orthanc. "StoreDicom" : true, - // DICOM associations are kept open as long as new DICOM commands - // are issued. This option sets the number of seconds of inactivity - // to wait before automatically closing a DICOM association. If set - // to 0, the connection is closed immediately. + // DICOM associations initiated by Lua scripts are kept open as long + // as new DICOM commands are issued. This option sets the number of + // seconds of inactivity to wait before automatically closing a + // DICOM association used by Lua. If set to 0, the connection is + // closed immediately. "DicomAssociationCloseDelay" : 5, // Maximum number of query/retrieve DICOM requests that are @@ -262,5 +356,69 @@ // will enable case-sensitive match for PN value representation // (such as PatientName). By default, the search is // case-insensitive, which does not follow the DICOM standard. - "CaseSensitivePN" : false + "CaseSensitivePN" : false, + + // Configure PKCS#11 to use hardware security modules (HSM) and + // smart cards when carrying on HTTPS client authentication. + /** + "Pkcs11" : { + "Module" : "/usr/local/lib/libbeidpkcs11.so", + "Module" : "C:/Windows/System32/beidpkcs11.dll", + "Pin" : "1234", + "Verbose" : true + } + **/ + + // If set to "true", Orthanc will still handle "SOP Classes in + // Study" (0008,0062) in C-FIND requests, even if the "SOP Class + // UID" metadata is not available in the database (which is the case + // if the DB was previously used by Orthanc <= 1.1.0). This option + // is turned off by default, as it requires intensive accesses to + // the hard drive. + "AllowFindSopClassesInStudy" : false, + + // If set to "false", Orthanc will not load its default dictionary + // of private tags. This might be necessary if you cannot import a + // DICOM file encoded using the Implicit VR Endian transfer syntax, + // and containing private tags: Such an import error might stem from + // a bad dictionary. You can still list your private tags of + // interest in the "Dictionary" configuration option below. + "LoadPrivateDictionary" : true, + + // Locale to be used by Orthanc. Currently, only used if comparing + // strings in a case-insensitive way. It should be safe to keep this + // value undefined, which lets Orthanc autodetect the suitable locale. + // "Locale" : "en_US.UTF-8", + + // Register a new tag in the dictionary of DICOM tags that are known + // to Orthanc. Each line must contain the tag (formatted as 2 + // hexadecimal numbers), the value representation (2 upcase + // characters), a nickname for the tag, possibly the minimum + // multiplicity (> 0 with defaults to 1), possibly the maximum + // multiplicity (0 means arbitrary multiplicity, defaults to 1), and + // possibly the Private Creator (for private tags). + "Dictionary" : { + // "0014,1020" : [ "DA", "ValidationExpiryDate", 1, 1 ] + // "00e1,10c2" : [ "UI", "PET-CT Multi Modality Name", 1, 1, "ELSCINT1" ] + // "7053,1003" : [ "ST", "Original Image Filename", 1, 1, "Philips PET Private Group" ] + // "2001,5f" : [ "SQ", "StackSequence", 1, 1, "Philips Imaging DD 001" ] + }, + + // Whether to run DICOM C-Move operations synchronously. If set to + // "false" (the default), each incoming C-Move request results in + // creating a new background job. Up to Orthanc 1.3.2, the implicit + // behavior was to use synchronous C-Move. + "SynchronousCMove" : false, + + // Maximum number of completed jobs that are kept in memory. A + // processing job is considered as complete once it is tagged as + // "Success" or "Failure". + "JobsHistorySize" : 10, + + // Specifies how Orthanc reacts when it receives a DICOM instance + // whose SOPInstanceUID is already stored. If set to "true", the new + // instance replaces the old one. If set to "false", the new + // instance is discarded and the old one is kept. Up to Orthanc + // 1.4.1, the implicit behavior corresponded to "false". + "OverwriteInstances" : false }
--- a/Resources/DicomConformanceStatement.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/DicomConformanceStatement.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # 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/DicomConformanceStatement.txt Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/DicomConformanceStatement.txt Fri Oct 12 15:18:10 2018 +0200 @@ -149,6 +149,7 @@ FINDPatientRootQueryRetrieveInformationModel | 1.2.840.10008.5.1.4.1.2.1.1 FINDStudyRootQueryRetrieveInformationModel | 1.2.840.10008.5.1.4.1.2.2.1 + FINDModalityWorklistInformationModel | 1.2.840.10008.5.1.4.31 -------------------- @@ -202,7 +203,7 @@ Transfer Syntaxes ----------------- -Orthanc will accept and negociate presentation contexts for all of the +Orthanc will accept and negotiate presentation contexts for all of the abovementioned supported SOP Classes using any of the following transfer syntaxes:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/DownloadOrthancFramework.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,327 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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/>. + + + +## +## Check whether the parent script sets the mandatory variables +## + +if (NOT DEFINED ORTHANC_FRAMEWORK_SOURCE OR + (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" AND + NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "web" AND + NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" AND + NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "path")) + message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_SOURCE must be set to \"hg\", \"web\", \"archive\" or \"path\"") +endif() + + +## +## Detection of the requested version +## + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR + ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR + ORTHANC_FRAMEWORK_SOURCE STREQUAL "web") + if (NOT DEFINED ORTHANC_FRAMEWORK_VERSION) + message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_VERSION must be set") + endif() + + if (DEFINED ORTHANC_FRAMEWORK_MAJOR OR + DEFINED ORTHANC_FRAMEWORK_MINOR OR + DEFINED ORTHANC_FRAMEWORK_REVISION OR + DEFINED ORTHANC_FRAMEWORK_MD5) + message(FATAL_ERROR "Some internal variable has been set") + endif() + + set(ORTHANC_FRAMEWORK_MD5 "") + + if (NOT DEFINED ORTHANC_FRAMEWORK_BRANCH) + if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_BRANCH "default") + + else() + set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}") + + set(RE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$") + string(REGEX REPLACE ${RE} "\\1" ORTHANC_FRAMEWORK_MAJOR ${ORTHANC_FRAMEWORK_VERSION}) + string(REGEX REPLACE ${RE} "\\2" ORTHANC_FRAMEWORK_MINOR ${ORTHANC_FRAMEWORK_VERSION}) + string(REGEX REPLACE ${RE} "\\3" ORTHANC_FRAMEWORK_REVISION ${ORTHANC_FRAMEWORK_VERSION}) + + if (NOT ORTHANC_FRAMEWORK_MAJOR MATCHES "^[0-9]+$" OR + NOT ORTHANC_FRAMEWORK_MINOR MATCHES "^[0-9]+$" OR + NOT ORTHANC_FRAMEWORK_REVISION MATCHES "^[0-9]+$") + message("Bad version of the Orthanc framework: ${ORTHANC_FRAMEWORK_VERSION}") + endif() + + if (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.1") + set(ORTHANC_FRAMEWORK_MD5 "dac95bd6cf86fb19deaf4e612961f378") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.2") + set(ORTHANC_FRAMEWORK_MD5 "d0ccdf68e855d8224331f13774992750") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.0") + set(ORTHANC_FRAMEWORK_MD5 "81e15f34d97ac32bbd7d26e85698835a") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.2") + set(ORTHANC_FRAMEWORK_MD5 "d1ee84927dcf668e60eb5868d24b9394") + endif() + endif() + endif() +endif() + + + +## +## Detection of the third-party software +## + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg") + find_program(ORTHANC_FRAMEWORK_HG hg) + + if (${ORTHANC_FRAMEWORK_HG} MATCHES "ORTHANC_FRAMEWORK_HG-NOTFOUND") + message(FATAL_ERROR "Please install Mercurial") + endif() +endif() + + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR + ORTHANC_FRAMEWORK_SOURCE STREQUAL "web") + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + find_program(ORTHANC_FRAMEWORK_7ZIP 7z + PATHS + "$ENV{ProgramFiles}/7-Zip" + "$ENV{ProgramW6432}/7-Zip" + ) + + if (${ORTHANC_FRAMEWORK_7ZIP} MATCHES "ORTHANC_FRAMEWORK_7ZIP-NOTFOUND") + message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)") + endif() + + else() + find_program(ORTHANC_FRAMEWORK_TAR tar) + if (${ORTHANC_FRAMEWORK_TAR} MATCHES "ORTHANC_FRAMEWORK_TAR-NOTFOUND") + message(FATAL_ERROR "Please install the 'tar' package") + endif() + endif() +endif() + + + +## +## Case of the Orthanc framework specified as a path on the filesystem +## + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "path") + if (NOT DEFINED ORTHANC_FRAMEWORK_ROOT) + message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ROOT must provide the path to the sources of Orthanc") + endif() + + if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}) + message(FATAL_ERROR "Non-existing directory: ${ORTHANC_FRAMEWORK_ROOT}") + endif() + + if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake) + message(FATAL_ERROR "Directory not containing the source code of Orthanc: ${ORTHANC_FRAMEWORK_ROOT}") + endif() + + set(ORTHANC_ROOT ${ORTHANC_FRAMEWORK_ROOT}) +endif() + + + +## +## Case of the Orthanc framework cloned using Mercurial +## + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg") + if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) + message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") + endif() + + set(ORTHANC_ROOT ${CMAKE_BINARY_DIR}/orthanc) + + if (EXISTS ${ORTHANC_ROOT}) + message("Updating the Orthanc source repository using Mercurial") + execute_process( + COMMAND ${ORTHANC_FRAMEWORK_HG} pull + WORKING_DIRECTORY ${ORTHANC_ROOT} + RESULT_VARIABLE Failure + ) + else() + message("Forking the Orthanc source repository using Mercurial") + execute_process( + COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://bitbucket.org/sjodogne/orthanc" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + endif() + + if (Failure OR NOT EXISTS ${ORTHANC_ROOT}) + message(FATAL_ERROR "Cannot fork the Orthanc repository") + endif() + + message("Setting branch of the Orthanc repository to: ${ORTHANC_FRAMEWORK_BRANCH}") + + execute_process( + COMMAND ${ORTHANC_FRAMEWORK_HG} update -c ${ORTHANC_FRAMEWORK_BRANCH} + WORKING_DIRECTORY ${ORTHANC_ROOT} + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while running Mercurial") + endif() +endif() + + + +## +## Case of the Orthanc framework provided as a source archive on the +## filesystem +## + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive") + if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE) + message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ARCHIVE must provide the path to the sources of Orthanc") + endif() +endif() + + + +## +## Case of the Orthanc framework downloaded from the Web +## + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "web") + if (DEFINED ORTHANC_FRAMEWORK_URL) + string(REGEX REPLACE "^.*/" "" ORTHANC_FRAMEMORK_FILENAME "${ORTHANC_FRAMEWORK_URL}") + else() + # Default case: Download from the official Web site + set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz) + #set(ORTHANC_FRAMEWORK_URL "http://www.orthanc-server.com/downloads/get.php?path=/orthanc/${ORTHANC_FRAMEMORK_FILENAME}") + set(ORTHANC_FRAMEWORK_URL "http://www.orthanc-server.com/downloads/third-party/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}") + endif() + + set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}") + + if (NOT EXISTS "${ORTHANC_FRAMEWORK_ARCHIVE}") + if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) + message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") + endif() + + message("Downloading: ${ORTHANC_FRAMEWORK_URL}") + + file(DOWNLOAD + "${ORTHANC_FRAMEWORK_URL}" "${ORTHANC_FRAMEWORK_ARCHIVE}" + SHOW_PROGRESS EXPECTED_MD5 "${ORTHANC_FRAMEWORK_MD5}" + TIMEOUT 60 + INACTIVITY_TIMEOUT 60 + ) + else() + message("Using local copy of: ${ORTHANC_FRAMEWORK_URL}") + endif() +endif() + + + + +## +## Uncompressing the Orthanc framework, if it was retrieved from a +## source archive on the filesystem, or from the official Web site +## + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR + ORTHANC_FRAMEWORK_SOURCE STREQUAL "web") + + if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE OR + NOT DEFINED ORTHANC_FRAMEWORK_VERSION OR + NOT DEFINED ORTHANC_FRAMEWORK_MD5) + message(FATAL_ERROR "Internal error") + endif() + + if (ORTHANC_FRAMEWORK_MD5 STREQUAL "") + message(FATAL_ERROR "Unknown release of Orthanc: ${ORTHANC_FRAMEWORK_VERSION}") + endif() + + file(MD5 ${ORTHANC_FRAMEWORK_ARCHIVE} ActualMD5) + + if (NOT "${ActualMD5}" STREQUAL "${ORTHANC_FRAMEWORK_MD5}") + message(FATAL_ERROR "The MD5 hash of the Orthanc archive is invalid: ${ORTHANC_FRAMEWORK_ARCHIVE}") + endif() + + set(ORTHANC_ROOT "${CMAKE_BINARY_DIR}/Orthanc-${ORTHANC_FRAMEWORK_VERSION}") + + if (NOT IS_DIRECTORY "${ORTHANC_ROOT}") + if (NOT ORTHANC_FRAMEWORK_ARCHIVE MATCHES ".tar.gz$") + message(FATAL_ERROR "Archive should have the \".tar.gz\" extension: ${ORTHANC_FRAMEWORK_ARCHIVE}") + endif() + + message("Uncompressing: ${ORTHANC_FRAMEWORK_ARCHIVE}") + + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + # How to silently extract files using 7-zip + # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly + + execute_process( + COMMAND ${ORTHANC_FRAMEWORK_7ZIP} e -y ${ORTHANC_FRAMEWORK_ARCHIVE} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + get_filename_component(TMP_FILENAME "${ORTHANC_FRAMEWORK_ARCHIVE}" NAME) + string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") + + execute_process( + COMMAND ${ORTHANC_FRAMEWORK_7ZIP} x -y ${TMP_FILENAME2} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + + else() + execute_process( + COMMAND sh -c "${ORTHANC_FRAMEWORK_TAR} xfz ${ORTHANC_FRAMEWORK_ARCHIVE}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + endif() + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if (NOT IS_DIRECTORY "${ORTHANC_ROOT}") + message(FATAL_ERROR "The Orthanc framework was not uncompressed at the proper location. Check the CMake instructions.") + endif() + endif() +endif()
--- a/Resources/EmbedResources.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/EmbedResources.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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,8 +40,8 @@ UPCASE_CHECK = True USE_SYSTEM_EXCEPTION = False EXCEPTION_CLASS = 'OrthancException' -OUT_OF_RANGE_EXCEPTION = 'OrthancException(ErrorCode_ParameterOutOfRange)' -INEXISTENT_PATH_EXCEPTION = 'OrthancException(ErrorCode_InexistentItem)' +OUT_OF_RANGE_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_ParameterOutOfRange)' +INEXISTENT_PATH_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_InexistentItem)' NAMESPACE = 'Orthanc' ARGS = [] @@ -159,6 +160,10 @@ #include <string> #include <list> +#if defined(_MSC_VER) +# pragma warning(disable: 4065) // "Switch statement contains 'default' but no 'case' labels" +#endif + namespace %s { namespace EmbeddedResources
--- a/Resources/EncodingTests.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/EncodingTests.h Fri Oct 12 15:18:10 2018 +0200 @@ -59,3 +59,5 @@ "\xd0\xa2\xd0\xb0\xd0\xb7", "\xd0\x9f\xd1\x80\xd1\x8f\xd0\xbc\xd0\xb0\xd1\x8f" }; +static const char *toUpperSource = "\x67\x72\xc3\xbc\xc3\x9f\x45\x4e\x20\x53\xc3\xa9\x62\x61\x73\x54\x49\x65\x6e\x20\x54\x65\x73\x74\xc3\xa9\xc3\xa4\xc3\xb6\xc3\xb2\xd0\x94\xce\x98\xc4\x9d\xd7\x93\xd8\xb5\xc4\xb7\xd1\x9b\xe0\xb9\x9b\xef\xbe\x88\xc4\xb0"; +static const char *toUpperResult = "\x47\x52\xc3\x9c\xc3\x9f\x45\x4e\x20\x53\xc3\x89\x42\x41\x53\x54\x49\x45\x4e\x20\x54\x45\x53\x54\xc3\x89\xc3\x84\xc3\x96\xc3\x92\xd0\x94\xce\x98\xc4\x9c\xd7\x93\xd8\xb5\xc4\xb6\xd0\x8b\xe0\xb9\x9b\xef\xbe\x88\xc4\xb0";
--- a/Resources/EncodingTests.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/EncodingTests.py Fri Oct 12 15:18:10 2018 +0200 @@ -72,3 +72,9 @@ for i in range(len(expected)): print expected[i] #print '%s: %s' % (expected[i], l[i]) + + + +u = (u'grüßEN SébasTIen %s' % source) +print 'static const char *toUpperSource = %s;' % ToArray(u.encode('utf-8')) +print 'static const char *toUpperResult = %s;' % ToArray(u.upper().encode('utf-8'))
--- a/Resources/ErrorCodes.json Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/ErrorCodes.json Fri Oct 12 15:18:10 2018 +0200 @@ -32,7 +32,7 @@ { "Code": 4, "Name": "NotEnoughMemory", - "Description": "Not enough memory" + "Description": "The server hosting Orthanc is running out of memory" }, { "Code": 5, @@ -189,7 +189,29 @@ "Code": 33, "Name": "EmptyRequest", "Description": "The request is empty" + }, + { + "Code": 34, + "HttpStatus": 406, + "Name": "NotAcceptable", + "Description": "Cannot send a response which is acceptable according to the Accept HTTP header" + }, + { + "Code": 35, + "Name": "NullPointer", + "Description": "Cannot handle a NULL pointer" }, + { + "Code": 36, + "HttpStatus": 503, + "Name": "DatabaseUnavailable", + "Description": "The database is currently not available (probably a transient situation)" + }, + { + "Code": 37, + "Name": "CanceledJob", + "Description": "This job was canceled" + }, @@ -321,12 +343,12 @@ { "Code": 2003, "Name": "HttpPortInUse", - "Description": "The TCP port of the HTTP server is already in use" + "Description": "The TCP port of the HTTP server is privileged or already in use" }, { "Code": 2004, "Name": "DicomPortInUse", - "Description": "The TCP port of the DICOM server is already in use" + "Description": "The TCP port of the DICOM server is privileged or already in use" }, { "Code": 2005, @@ -507,5 +529,15 @@ "Code": 2040, "Name": "CannotOrderSlices", "Description": "Unable to order the slices of the series" + }, + { + "Code": 2041, + "Name": "NoWorklistHandler", + "Description": "No request handler factory for DICOM C-Find Modality SCP" + }, + { + "Code": 2042, + "Name": "AlreadyExistingTag", + "Description": "Cannot override the value of a tag that already exists" } ]
--- a/Resources/Fonts/GenerateFont.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Fonts/GenerateFont.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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/GenerateAnonymizationProfile.py Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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/>. + + +import re +import sys +import xml.etree.ElementTree as ET + +# Usage: +# ./GenerateAnonymizationProfile.py ~/Subversion/dicom-specification/2017c/part15.xml + +if len(sys.argv) != 2: + raise Exception('Please provide the path to the part15.xml file from the DICOM standard') + +with open(sys.argv[1], 'r') as f: + root = ET.fromstring(f.read()) + +br = '{http://docbook.org/ns/docbook}' # Shorthand variable + + +LINES = [] + +def FormatLine(command, name): + indentation = 65 + + if len(command) > indentation: + raise Exception('Too long command') + + line = ' ' + command + (' ' * (indentation - len(command))) + '// ' + name + LINES.append(line) + +def FormatUnknown(rawTag, name, profile): + FormatLine('// TODO: %s with rule %s' % (rawTag, profile), name) + + +RAW_TAG_RE = re.compile(r'^\(\s*([0-9A-F]{4})\s*,\s*([0-9A-F]{4})\s*\)$') + + +for table in root.iter('%stable' % br): + if table.attrib['label'] == 'E.1-1': + for row in table.find('%stbody' % br).iter('%str' % br): + rawTag = row.find('%std[2]/%spara' % (br, br)).text + name = row.find('%std[1]/%spara' % (br, br)).text + profile = row.find('%std[5]/%spara' % (br, br)).text + + if len(name.strip()) == 0: + continue + + match = RAW_TAG_RE.match(rawTag) + if match == None: + FormatUnknown(rawTag, name, profile) + else: + tag = '0x%s, 0x%s' % (match.group(1).lower(), match.group(2).lower()) + + if name in [ + 'SOP Instance UID', + 'Series Instance UID', + 'Study Instance UID', + ]: + FormatLine('// Tag (%s) is set in Apply() /* %s */' % (tag, profile), name) + elif name in [ + 'Referenced Image Sequence', + 'Source Image Sequence', + 'Referenced SOP Instance UID', + 'Frame of Reference UID', + 'Referenced Frame of Reference UID', + 'Related Frame of Reference UID', + ]: + FormatLine('// Tag (%s) => RelationshipsVisitor /* %s */' % (tag, profile), name) + elif name in [ + 'Patient\'s Name', + 'Patient ID', + ]: + FormatLine('// Tag (%s) is set below (*) /* %s */' % (tag, profile), name) + elif profile == 'X': + FormatLine('removals_.insert(DicomTag(%s));' % tag, name) + elif profile.startswith('X/'): + FormatLine('removals_.insert(DicomTag(%s)); /* %s */' % (tag, profile), name) + elif profile == 'Z': + FormatLine('clearings_.insert(DicomTag(%s));' % tag, name) + elif profile == 'D' or profile.startswith('Z/'): + FormatLine('clearings_.insert(DicomTag(%s)); /* %s */' % (tag, profile), name) + elif profile == 'U': + FormatLine('removals_.insert(DicomTag(%s)); /* TODO UID */' % (tag), name) + else: + FormatUnknown(rawTag, name, profile) + +for line in sorted(LINES): + print line + + +# D - replace with a non-zero length value that may be a dummy value and consistent with the VR +# Z - replace with a zero length value, or a non-zero length value that may be a dummy value and consistent with the VR +# X - remove +# K - keep (unchanged for non-sequence attributes, cleaned for sequences) +# C - clean, that is replace with values of similar meaning known not to contain identifying information and consistent with the VR +# U - replace with a non-zero length UID that is internally consistent within a set of Instances +# Z/D - Z unless D is required to maintain IOD conformance (Type 2 versus Type 1) +# X/Z - X unless Z is required to maintain IOD conformance (Type 3 versus Type 2) +# X/D - X unless D is required to maintain IOD conformance (Type 3 versus Type 1) +# X/Z/D - X unless Z or D is required to maintain IOD conformance (Type 3 versus Type 2 versus Type 1) +# X/Z/U* - X unless Z or replacement of contained instance UIDs (U) is required to maintain IOD conformance (Type 3 versus Type 2 versus Type 1 sequences containing UID references)
--- a/Resources/GenerateErrorCodes.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/GenerateErrorCodes.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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/DatabasePluginSample/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 2.8) + +project(SampleDatabasePlugin) + +# Parameters of the build +SET(SAMPLE_DATABASE_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(STANDALONE_BUILD ON) + +# Advanced parameters to fine-tune linking against system libraries +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +SET(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite") + +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) + +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/SQLiteConfiguration.cmake) + +EmbedResources( + --system-exception # Use "std::runtime_error" instead of "OrthancException" for embedded resources + PREPARE_DATABASE ${ORTHANC_ROOT}/OrthancServer/PrepareDatabase.sql + ) + +message("Setting the version of the plugin to ${SAMPLE_DATABASE_VERSION}") + +add_definitions( + -DORTHANC_SQLITE_STANDALONE=1 + -DORTHANC_ENABLE_BASE64=0 + -DORTHANC_ENABLE_LOGGING=0 + -DORTHANC_ENABLE_MD5=0 + -DORTHANC_ENABLE_PLUGINS=1 + -DORTHANC_ENABLE_PUGIXML=0 + -DORTHANC_SANDBOXED=0 + -DSAMPLE_DATABASE_VERSION="${SAMPLE_DATABASE_VERSION}" + ) + +add_library(SampleDatabase SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${SQLITE_SOURCES} + ${AUTOGENERATED_SOURCES} + + ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp + ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/SQLite/Connection.cpp + ${ORTHANC_ROOT}/Core/SQLite/FunctionContext.cpp + ${ORTHANC_ROOT}/Core/SQLite/Statement.cpp + ${ORTHANC_ROOT}/Core/SQLite/StatementId.cpp + ${ORTHANC_ROOT}/Core/SQLite/StatementReference.cpp + ${ORTHANC_ROOT}/Core/SQLite/Transaction.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/OrthancServer/DatabaseWrapperBase.cpp + ${ORTHANC_ROOT}/Plugins/Engine/PluginsEnumerations.cpp + + Database.cpp + Plugin.cpp + ) + +set_target_properties(SampleDatabase PROPERTIES + VERSION ${SAMPLE_DATABASE_VERSION} + SOVERSION ${SAMPLE_DATABASE_VERSION}) + +install( + TARGETS SampleDatabase + 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/Resources/Graveyard/DatabasePluginSample/Database.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,565 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "Database.h" + +#include "../../../Core/DicomFormat/DicomArray.h" + +#include <EmbeddedResources.h> +#include <boost/lexical_cast.hpp> + + +namespace Internals +{ + class SignalFileDeleted : public Orthanc::SQLite::IScalarFunction + { + private: + OrthancPlugins::DatabaseBackendOutput& output_; + + public: + SignalFileDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output) + { + } + + virtual const char* GetName() const + { + return "SignalFileDeleted"; + } + + virtual unsigned int GetCardinality() const + { + return 7; + } + + virtual void Compute(Orthanc::SQLite::FunctionContext& context) + { + std::string uncompressedMD5, compressedMD5; + + if (!context.IsNullValue(5)) + { + uncompressedMD5 = context.GetStringValue(5); + } + + if (!context.IsNullValue(6)) + { + compressedMD5 = context.GetStringValue(6); + } + + output_.SignalDeletedAttachment(context.GetStringValue(0), + context.GetIntValue(1), + context.GetInt64Value(2), + uncompressedMD5, + context.GetIntValue(3), + context.GetInt64Value(4), + compressedMD5); + } + }; + + + class SignalResourceDeleted : public Orthanc::SQLite::IScalarFunction + { + private: + OrthancPlugins::DatabaseBackendOutput& output_; + + public: + SignalResourceDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output) + { + } + + virtual const char* GetName() const + { + return "SignalResourceDeleted"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(Orthanc::SQLite::FunctionContext& context) + { + output_.SignalDeletedResource(context.GetStringValue(0), + Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1)))); + } + }; +} + + +class Database::SignalRemainingAncestor : public Orthanc::SQLite::IScalarFunction +{ +private: + bool hasRemainingAncestor_; + std::string remainingPublicId_; + OrthancPluginResourceType remainingType_; + +public: + SignalRemainingAncestor() : + hasRemainingAncestor_(false), + remainingType_(OrthancPluginResourceType_Instance) // Some dummy value + { + } + + void Reset() + { + hasRemainingAncestor_ = false; + } + + virtual const char* GetName() const + { + return "SignalRemainingAncestor"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(Orthanc::SQLite::FunctionContext& context) + { + if (!hasRemainingAncestor_ || + remainingType_ >= context.GetIntValue(1)) + { + hasRemainingAncestor_ = true; + remainingPublicId_ = context.GetStringValue(0); + remainingType_ = Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1))); + } + } + + bool HasRemainingAncestor() const + { + return hasRemainingAncestor_; + } + + const std::string& GetRemainingAncestorId() const + { + assert(hasRemainingAncestor_); + return remainingPublicId_; + } + + OrthancPluginResourceType GetRemainingAncestorType() const + { + assert(hasRemainingAncestor_); + return remainingType_; + } +}; + + + +Database::Database(const std::string& path) : + path_(path), + base_(db_), + signalRemainingAncestor_(NULL) +{ +} + + +void Database::Open() +{ + db_.Open(path_); + + db_.Execute("PRAGMA ENCODING=\"UTF-8\";"); + + // http://www.sqlite.org/pragma.html + db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;"); + db_.Execute("PRAGMA JOURNAL_MODE=WAL;"); + db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;"); + db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;"); + //db_.Execute("PRAGMA TEMP_STORE=memory"); + + if (!db_.DoesTableExist("GlobalProperties")) + { + std::string query; + Orthanc::EmbeddedResources::GetFileResource(query, Orthanc::EmbeddedResources::PREPARE_DATABASE); + db_.Execute(query); + } + + signalRemainingAncestor_ = new SignalRemainingAncestor; + db_.Register(signalRemainingAncestor_); + db_.Register(new Internals::SignalFileDeleted(GetOutput())); + db_.Register(new Internals::SignalResourceDeleted(GetOutput())); +} + + +void Database::Close() +{ + db_.Close(); +} + + +void Database::AddAttachment(int64_t id, + const OrthancPluginAttachment& attachment) +{ + Orthanc::FileInfo info(attachment.uuid, + static_cast<Orthanc::FileContentType>(attachment.contentType), + attachment.uncompressedSize, + attachment.uncompressedHash, + static_cast<Orthanc::CompressionType>(attachment.compressionType), + attachment.compressedSize, + attachment.compressedHash); + base_.AddAttachment(id, info); +} + + +void Database::DeleteResource(int64_t id) +{ + signalRemainingAncestor_->Reset(); + + Orthanc::SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); + s.BindInt64(0, id); + s.Run(); + + if (signalRemainingAncestor_->HasRemainingAncestor()) + { + GetOutput().SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorId(), + signalRemainingAncestor_->GetRemainingAncestorType()); + } +} + + +static void Answer(OrthancPlugins::DatabaseBackendOutput& output, + const Orthanc::ServerIndexChange& change) +{ + output.AnswerChange(change.GetSeq(), + change.GetChangeType(), + Orthanc::Plugins::Convert(change.GetResourceType()), + change.GetPublicId(), + change.GetDate()); +} + + +static void Answer(OrthancPlugins::DatabaseBackendOutput& output, + const Orthanc::ExportedResource& resource) +{ + output.AnswerExportedResource(resource.GetSeq(), + Orthanc::Plugins::Convert(resource.GetResourceType()), + resource.GetPublicId(), + resource.GetModality(), + resource.GetDate(), + resource.GetPatientId(), + resource.GetStudyInstanceUid(), + resource.GetSeriesInstanceUid(), + resource.GetSopInstanceUid()); +} + + +void Database::GetChanges(bool& done /*out*/, + int64_t since, + uint32_t maxResults) +{ + typedef std::list<Orthanc::ServerIndexChange> Changes; + + Changes changes; + base_.GetChanges(changes, done, since, maxResults); + + for (Changes::const_iterator it = changes.begin(); it != changes.end(); ++it) + { + Answer(GetOutput(), *it); + } +} + + +void Database::GetExportedResources(bool& done /*out*/, + int64_t since, + uint32_t maxResults) +{ + typedef std::list<Orthanc::ExportedResource> Resources; + + Resources resources; + base_.GetExportedResources(resources, done, since, maxResults); + + for (Resources::const_iterator it = resources.begin(); it != resources.end(); ++it) + { + Answer(GetOutput(), *it); + } +} + + +void Database::GetLastChange() +{ + std::list<Orthanc::ServerIndexChange> change; + Orthanc::ErrorCode code = base_.GetLastChange(change); + + if (code != Orthanc::ErrorCode_Success) + { + throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code)); + } + + if (!change.empty()) + { + Answer(GetOutput(), change.front()); + } +} + + +void Database::GetLastExportedResource() +{ + std::list<Orthanc::ExportedResource> resource; + base_.GetLastExportedResource(resource); + + if (!resource.empty()) + { + Answer(GetOutput(), resource.front()); + } +} + + +void Database::GetMainDicomTags(int64_t id) +{ + Orthanc::DicomMap tags; + base_.GetMainDicomTags(tags, id); + + Orthanc::DicomArray arr(tags); + for (size_t i = 0; i < arr.GetSize(); i++) + { + GetOutput().AnswerDicomTag(arr.GetElement(i).GetTag().GetGroup(), + arr.GetElement(i).GetTag().GetElement(), + arr.GetElement(i).GetValue().GetContent()); + } +} + + +std::string Database::GetPublicId(int64_t resourceId) +{ + std::string id; + if (base_.GetPublicId(id, resourceId)) + { + return id; + } + else + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_UnknownResource); + } +} + + +OrthancPluginResourceType Database::GetResourceType(int64_t resourceId) +{ + Orthanc::ResourceType result; + Orthanc::ErrorCode code = base_.GetResourceType(result, resourceId); + + if (code == Orthanc::ErrorCode_Success) + { + return Orthanc::Plugins::Convert(result); + } + else + { + throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code)); + } +} + + + +template <typename I> +static void ConvertList(std::list<int32_t>& target, + const std::list<I>& source) +{ + for (typename std::list<I>::const_iterator + it = source.begin(); it != source.end(); ++it) + { + target.push_back(*it); + } +} + + +void Database::ListAvailableMetadata(std::list<int32_t>& target /*out*/, + int64_t id) +{ + std::list<Orthanc::MetadataType> tmp; + base_.ListAvailableMetadata(tmp, id); + ConvertList(target, tmp); +} + + +void Database::ListAvailableAttachments(std::list<int32_t>& target /*out*/, + int64_t id) +{ + std::list<Orthanc::FileContentType> tmp; + base_.ListAvailableAttachments(tmp, id); + ConvertList(target, tmp); +} + + +void Database::LogChange(const OrthancPluginChange& change) +{ + int64_t id; + OrthancPluginResourceType type; + if (!LookupResource(id, type, change.publicId) || + type != change.resourceType) + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_DatabasePlugin); + } + + Orthanc::ServerIndexChange tmp(change.seq, + static_cast<Orthanc::ChangeType>(change.changeType), + Orthanc::Plugins::Convert(change.resourceType), + change.publicId, + change.date); + + base_.LogChange(id, tmp); +} + + +void Database::LogExportedResource(const OrthancPluginExportedResource& resource) +{ + Orthanc::ExportedResource tmp(resource.seq, + Orthanc::Plugins::Convert(resource.resourceType), + resource.publicId, + resource.modality, + resource.date, + resource.patientId, + resource.studyInstanceUid, + resource.seriesInstanceUid, + resource.sopInstanceUid); + + base_.LogExportedResource(tmp); +} + + +bool Database::LookupAttachment(int64_t id, + int32_t contentType) +{ + Orthanc::FileInfo attachment; + if (base_.LookupAttachment(attachment, id, static_cast<Orthanc::FileContentType>(contentType))) + { + GetOutput().AnswerAttachment(attachment.GetUuid(), + attachment.GetContentType(), + attachment.GetUncompressedSize(), + attachment.GetUncompressedMD5(), + attachment.GetCompressionType(), + attachment.GetCompressedSize(), + attachment.GetCompressedMD5()); + return true; + } + else + { + return false; + } +} + + +bool Database::LookupParent(int64_t& parentId /*out*/, + int64_t resourceId) +{ + bool found; + Orthanc::ErrorCode code = base_.LookupParent(found, parentId, resourceId); + + if (code == Orthanc::ErrorCode_Success) + { + return found; + } + else + { + throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code)); + } +} + + +bool Database::LookupResource(int64_t& id /*out*/, + OrthancPluginResourceType& type /*out*/, + const char* publicId) +{ + Orthanc::ResourceType tmp; + if (base_.LookupResource(id, tmp, publicId)) + { + type = Orthanc::Plugins::Convert(tmp); + return true; + } + else + { + return false; + } +} + + +void Database::StartTransaction() +{ + transaction_.reset(new Orthanc::SQLite::Transaction(db_)); + transaction_->Begin(); +} + + +void Database::RollbackTransaction() +{ + transaction_->Rollback(); + transaction_.reset(NULL); +} + + +void Database::CommitTransaction() +{ + transaction_->Commit(); + transaction_.reset(NULL); +} + + +uint32_t Database::GetDatabaseVersion() +{ + std::string version; + + if (!LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion)) + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError); + } + + try + { + return boost::lexical_cast<uint32_t>(version); + } + catch (boost::bad_lexical_cast&) + { + throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError); + } +} + + +void Database::UpgradeDatabase(uint32_t targetVersion, + OrthancPluginStorageArea* storageArea) +{ + if (targetVersion == 6) + { + OrthancPluginErrorCode code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, + OrthancPluginResourceType_Study); + if (code == OrthancPluginErrorCode_Success) + { + code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, + OrthancPluginResourceType_Series); + } + + if (code != OrthancPluginErrorCode_Success) + { + throw OrthancPlugins::DatabaseException(code); + } + + base_.SetGlobalProperty(Orthanc::GlobalProperty_DatabaseSchemaVersion, "6"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/DatabasePluginSample/Database.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,285 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <orthanc/OrthancCppDatabasePlugin.h> + +#include "../../../Core/SQLite/Connection.h" +#include "../../../Core/SQLite/Transaction.h" +#include "../../../OrthancServer/DatabaseWrapperBase.h" +#include "../../Engine/PluginsEnumerations.h" + +#include <memory> + +class Database : public OrthancPlugins::IDatabaseBackend +{ +private: + class SignalRemainingAncestor; + + std::string path_; + Orthanc::SQLite::Connection db_; + Orthanc::DatabaseWrapperBase base_; + SignalRemainingAncestor* signalRemainingAncestor_; + + std::auto_ptr<Orthanc::SQLite::Transaction> transaction_; + +public: + Database(const std::string& path); + + virtual void Open(); + + virtual void Close(); + + virtual void AddAttachment(int64_t id, + const OrthancPluginAttachment& attachment); + + virtual void AttachChild(int64_t parent, + int64_t child) + { + base_.AttachChild(parent, child); + } + + virtual void ClearChanges() + { + db_.Execute("DELETE FROM Changes"); + } + + virtual void ClearExportedResources() + { + db_.Execute("DELETE FROM ExportedResources"); + } + + virtual int64_t CreateResource(const char* publicId, + OrthancPluginResourceType type) + { + return base_.CreateResource(publicId, Orthanc::Plugins::Convert(type)); + } + + virtual void DeleteAttachment(int64_t id, + int32_t attachment) + { + base_.DeleteAttachment(id, static_cast<Orthanc::FileContentType>(attachment)); + } + + virtual void DeleteMetadata(int64_t id, + int32_t metadataType) + { + base_.DeleteMetadata(id, static_cast<Orthanc::MetadataType>(metadataType)); + } + + virtual void DeleteResource(int64_t id); + + virtual void GetAllInternalIds(std::list<int64_t>& target, + OrthancPluginResourceType resourceType) + { + base_.GetAllInternalIds(target, Orthanc::Plugins::Convert(resourceType)); + } + + virtual void GetAllPublicIds(std::list<std::string>& target, + OrthancPluginResourceType resourceType) + { + base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType)); + } + + virtual void GetAllPublicIds(std::list<std::string>& target, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit) + { + base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType), since, limit); + } + + virtual void GetChanges(bool& done /*out*/, + int64_t since, + uint32_t maxResults); + + virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/, + int64_t id) + { + base_.GetChildrenInternalId(target, id); + } + + virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/, + int64_t id) + { + base_.GetChildrenPublicId(target, id); + } + + virtual void GetExportedResources(bool& done /*out*/, + int64_t since, + uint32_t maxResults); + + virtual void GetLastChange(); + + virtual void GetLastExportedResource(); + + virtual void GetMainDicomTags(int64_t id); + + virtual std::string GetPublicId(int64_t resourceId); + + virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) + { + return base_.GetResourceCount(Orthanc::Plugins::Convert(resourceType)); + } + + virtual OrthancPluginResourceType GetResourceType(int64_t resourceId); + + virtual uint64_t GetTotalCompressedSize() + { + return base_.GetTotalCompressedSize(); + } + + virtual uint64_t GetTotalUncompressedSize() + { + return base_.GetTotalUncompressedSize(); + } + + virtual bool IsExistingResource(int64_t internalId) + { + return base_.IsExistingResource(internalId); + } + + virtual bool IsProtectedPatient(int64_t internalId) + { + return base_.IsProtectedPatient(internalId); + } + + virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/, + int64_t id); + + virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/, + int64_t id); + + virtual void LogChange(const OrthancPluginChange& change); + + virtual void LogExportedResource(const OrthancPluginExportedResource& resource); + + virtual bool LookupAttachment(int64_t id, + int32_t contentType); + + virtual bool LookupGlobalProperty(std::string& target /*out*/, + int32_t property) + { + return base_.LookupGlobalProperty(target, static_cast<Orthanc::GlobalProperty>(property)); + } + + virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, + OrthancPluginResourceType level, + uint16_t group, + uint16_t element, + OrthancPluginIdentifierConstraint constraint, + const char* value) + { + base_.LookupIdentifier(target, Orthanc::Plugins::Convert(level), + Orthanc::DicomTag(group, element), + Orthanc::Plugins::Convert(constraint), value); + } + + virtual bool LookupMetadata(std::string& target /*out*/, + int64_t id, + int32_t metadataType) + { + return base_.LookupMetadata(target, id, static_cast<Orthanc::MetadataType>(metadataType)); + } + + virtual bool LookupParent(int64_t& parentId /*out*/, + int64_t resourceId); + + virtual bool LookupResource(int64_t& id /*out*/, + OrthancPluginResourceType& type /*out*/, + const char* publicId); + + virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) + { + return base_.SelectPatientToRecycle(internalId); + } + + virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/, + int64_t patientIdToAvoid) + { + return base_.SelectPatientToRecycle(internalId, patientIdToAvoid); + } + + + virtual void SetGlobalProperty(int32_t property, + const char* value) + { + base_.SetGlobalProperty(static_cast<Orthanc::GlobalProperty>(property), value); + } + + virtual void SetMainDicomTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) + { + base_.SetMainDicomTag(id, Orthanc::DicomTag(group, element), value); + } + + virtual void SetIdentifierTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) + { + base_.SetIdentifierTag(id, Orthanc::DicomTag(group, element), value); + } + + virtual void SetMetadata(int64_t id, + int32_t metadataType, + const char* value) + { + base_.SetMetadata(id, static_cast<Orthanc::MetadataType>(metadataType), value); + } + + virtual void SetProtectedPatient(int64_t internalId, + bool isProtected) + { + base_.SetProtectedPatient(internalId, isProtected); + } + + virtual void StartTransaction(); + + virtual void RollbackTransaction(); + + virtual void CommitTransaction(); + + virtual uint32_t GetDatabaseVersion(); + + virtual void UpgradeDatabase(uint32_t targetVersion, + OrthancPluginStorageArea* storageArea); + + virtual void ClearMainDicomTags(int64_t internalId) + { + base_.ClearMainDicomTags(internalId); + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,757 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DatabaseWrapperBase.h" + +#include <stdio.h> +#include <memory> + +namespace Orthanc +{ + void DatabaseWrapperBase::SetGlobalProperty(GlobalProperty property, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)"); + s.BindInt(0, property); + s.BindString(1, value); + s.Run(); + } + + bool DatabaseWrapperBase::LookupGlobalProperty(std::string& target, + GlobalProperty property) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM GlobalProperties WHERE property=?"); + s.BindInt(0, property); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + int64_t DatabaseWrapperBase::CreateResource(const std::string& publicId, + ResourceType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); + s.BindInt(0, type); + s.BindString(1, publicId); + s.Run(); + return db_.GetLastInsertRowId(); + } + + bool DatabaseWrapperBase::LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT internalId, resourceType FROM Resources WHERE publicId=?"); + s.BindString(0, publicId); + + if (!s.Step()) + { + return false; + } + else + { + id = s.ColumnInt(0); + type = static_cast<ResourceType>(s.ColumnInt(1)); + + // Check whether there is a single resource with this public id + assert(!s.Step()); + + return true; + } + } + + ErrorCode DatabaseWrapperBase::LookupParent(bool& found, + int64_t& parentId, + int64_t resourceId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT parentId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (!s.Step()) + { + return ErrorCode_UnknownResource; + } + + if (s.ColumnIsNull(0)) + { + found = false; + } + else + { + found = true; + parentId = s.ColumnInt(0); + } + + return ErrorCode_Success; + } + + bool DatabaseWrapperBase::GetPublicId(std::string& result, + int64_t resourceId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT publicId FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (!s.Step()) + { + return false; + } + else + { + result = s.ColumnString(0); + return true; + } + } + + + ErrorCode DatabaseWrapperBase::GetResourceType(ResourceType& result, + int64_t resourceId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT resourceType FROM Resources WHERE internalId=?"); + s.BindInt64(0, resourceId); + + if (s.Step()) + { + result = static_cast<ResourceType>(s.ColumnInt(0)); + return ErrorCode_Success; + } + else + { + return ErrorCode_UnknownResource; + } + } + + + void DatabaseWrapperBase::AttachChild(int64_t parent, + int64_t child) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?"); + s.BindInt64(0, parent); + s.BindInt64(1, child); + s.Run(); + } + + + void DatabaseWrapperBase::SetMetadata(int64_t id, + MetadataType type, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.BindString(2, value); + s.Run(); + } + + void DatabaseWrapperBase::DeleteMetadata(int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + s.Run(); + } + + bool DatabaseWrapperBase::LookupMetadata(std::string& target, + int64_t id, + MetadataType type) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM Metadata WHERE id=? AND type=?"); + s.BindInt64(0, id); + s.BindInt(1, type); + + if (!s.Step()) + { + return false; + } + else + { + target = s.ColumnString(0); + return true; + } + } + + void DatabaseWrapperBase::ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.push_back(static_cast<MetadataType>(s.ColumnInt(0))); + } + } + + + void DatabaseWrapperBase::AddAttachment(int64_t id, + const FileInfo& attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, attachment.GetContentType()); + s.BindString(2, attachment.GetUuid()); + s.BindInt64(3, attachment.GetCompressedSize()); + s.BindInt64(4, attachment.GetUncompressedSize()); + s.BindInt(5, attachment.GetCompressionType()); + s.BindString(6, attachment.GetUncompressedMD5()); + s.BindString(7, attachment.GetCompressedMD5()); + s.Run(); + } + + + void DatabaseWrapperBase::DeleteAttachment(int64_t id, + FileContentType attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, attachment); + s.Run(); + } + + + + void DatabaseWrapperBase::ListAvailableAttachments(std::list<FileContentType>& target, + int64_t id) + { + target.clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT fileType FROM AttachedFiles WHERE id=?"); + s.BindInt64(0, id); + + while (s.Step()) + { + target.push_back(static_cast<FileContentType>(s.ColumnInt(0))); + } + } + + bool DatabaseWrapperBase::LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, contentType); + + if (!s.Step()) + { + return false; + } + else + { + attachment = FileInfo(s.ColumnString(0), + contentType, + s.ColumnInt64(1), + s.ColumnString(4), + static_cast<CompressionType>(s.ColumnInt(2)), + s.ColumnInt64(3), + s.ColumnString(5)); + return true; + } + } + + + void DatabaseWrapperBase::ClearMainDicomTags(int64_t id) + { + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + s.Run(); + } + } + + + void DatabaseWrapperBase::SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); + s.Run(); + } + + + void DatabaseWrapperBase::SetIdentifierTag(int64_t id, + const DicomTag& tag, + const std::string& value) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)"); + s.BindInt64(0, id); + s.BindInt(1, tag.GetGroup()); + s.BindInt(2, tag.GetElement()); + s.BindString(3, value); + s.Run(); + } + + + void DatabaseWrapperBase::GetMainDicomTags(DicomMap& map, + int64_t id) + { + map.Clear(); + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?"); + s.BindInt64(0, id); + while (s.Step()) + { + map.SetValue(s.ColumnInt(1), + s.ColumnInt(2), + s.ColumnString(3), false); + } + } + + + + void DatabaseWrapperBase::GetChildrenPublicId(std::list<std::string>& target, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + void DatabaseWrapperBase::GetChildrenInternalId(std::list<int64_t>& target, + int64_t id) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b " + "WHERE a.parentId = b.internalId AND b.internalId = ?"); + s.BindInt64(0, id); + + target.clear(); + + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapperBase::LogChange(int64_t internalId, + const ServerIndexChange& change) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)"); + s.BindInt(0, change.GetChangeType()); + s.BindInt64(1, internalId); + s.BindInt(2, change.GetResourceType()); + s.BindString(3, change.GetDate()); + s.Run(); + } + + + ErrorCode DatabaseWrapperBase::GetChangesInternal(std::list<ServerIndexChange>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) + { + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); + ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); + const std::string& date = s.ColumnString(4); + + int64_t internalId = s.ColumnInt64(2); + std::string publicId; + if (!GetPublicId(publicId, internalId)) + { + return ErrorCode_UnknownResource; + } + + target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); + } + + done = !(target.size() == maxResults && s.Step()); + return ErrorCode_Success; + } + + + ErrorCode DatabaseWrapperBase::GetChanges(std::list<ServerIndexChange>& target, + bool& done, + int64_t since, + uint32_t maxResults) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + return GetChangesInternal(target, done, s, maxResults); + } + + ErrorCode DatabaseWrapperBase::GetLastChange(std::list<ServerIndexChange>& target) + { + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); + return GetChangesInternal(target, done, s, 1); + } + + + void DatabaseWrapperBase::LogExportedResource(const ExportedResource& resource) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)"); + + s.BindInt(0, resource.GetResourceType()); + s.BindString(1, resource.GetPublicId()); + s.BindString(2, resource.GetModality()); + s.BindString(3, resource.GetPatientId()); + s.BindString(4, resource.GetStudyInstanceUid()); + s.BindString(5, resource.GetSeriesInstanceUid()); + s.BindString(6, resource.GetSopInstanceUid()); + s.BindString(7, resource.GetDate()); + s.Run(); + } + + + void DatabaseWrapperBase::GetExportedResourcesInternal(std::list<ExportedResource>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults) + { + target.clear(); + + while (target.size() < maxResults && s.Step()) + { + int64_t seq = s.ColumnInt64(0); + ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); + std::string publicId = s.ColumnString(2); + + ExportedResource resource(seq, + resourceType, + publicId, + s.ColumnString(3), // modality + s.ColumnString(8), // date + s.ColumnString(4), // patient ID + s.ColumnString(5), // study instance UID + s.ColumnString(6), // series instance UID + s.ColumnString(7)); // sop instance UID + + target.push_back(resource); + } + + done = !(target.size() == maxResults && s.Step()); + } + + + void DatabaseWrapperBase::GetExportedResources(std::list<ExportedResource>& target, + bool& done, + int64_t since, + uint32_t maxResults) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); + s.BindInt64(0, since); + s.BindInt(1, maxResults + 1); + GetExportedResourcesInternal(target, done, s, maxResults); + } + + + void DatabaseWrapperBase::GetLastExportedResource(std::list<ExportedResource>& target) + { + bool done; // Ignored + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); + GetExportedResourcesInternal(target, done, s, 1); + } + + + + uint64_t DatabaseWrapperBase::GetTotalCompressedSize() + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles"); + s.Run(); + return static_cast<uint64_t>(s.ColumnInt64(0)); + } + + + uint64_t DatabaseWrapperBase::GetTotalUncompressedSize() + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles"); + s.Run(); + return static_cast<uint64_t>(s.ColumnInt64(0)); + } + + void DatabaseWrapperBase::GetAllInternalIds(std::list<int64_t>& target, + ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT internalId FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnInt64(0)); + } + } + + + void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) + { + if (limit == 0) + { + target.clear(); + return; + } + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?"); + s.BindInt(0, resourceType); + s.BindInt64(1, limit); + s.BindInt64(2, since); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } + } + + + uint64_t DatabaseWrapperBase::GetResourceCount(ResourceType resourceType) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT COUNT(*) FROM Resources WHERE resourceType=?"); + s.BindInt(0, resourceType); + + if (!s.Step()) + { + return 0; + } + else + { + int64_t c = s.ColumnInt(0); + assert(!s.Step()); + return c; + } + } + + + bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1"); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT patientId FROM PatientRecyclingOrder " + "WHERE patientId != ? ORDER BY seq ASC LIMIT 1"); + s.BindInt64(0, patientIdToAvoid); + + if (!s.Step()) + { + // No patient remaining or all the patients are protected + return false; + } + else + { + internalId = s.ColumnInt(0); + return true; + } + } + + bool DatabaseWrapperBase::IsProtectedPatient(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?"); + s.BindInt64(0, internalId); + return !s.Step(); + } + + void DatabaseWrapperBase::SetProtectedPatient(int64_t internalId, + bool isProtected) + { + if (isProtected) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?"); + s.BindInt64(0, internalId); + s.Run(); + } + else if (IsProtectedPatient(internalId)) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)"); + s.BindInt64(0, internalId); + s.Run(); + } + else + { + // Nothing to do: The patient is already unprotected + } + } + + + + bool DatabaseWrapperBase::IsExistingResource(int64_t internalId) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT * FROM Resources WHERE internalId=?"); + s.BindInt64(0, internalId); + return s.Step(); + } + + + + void DatabaseWrapperBase::LookupIdentifier(std::list<int64_t>& target, + ResourceType level, + const DicomTag& tag, + IdentifierConstraintType type, + const std::string& value) + { + static const char* COMMON = ("SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " + "d.id = r.internalId AND r.resourceType=? AND " + "d.tagGroup=? AND d.tagElement=? AND "); + + std::auto_ptr<SQLite::Statement> s; + + switch (type) + { + case IdentifierConstraintType_GreaterOrEqual: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value>=?")); + break; + + case IdentifierConstraintType_SmallerOrEqual: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value<=?")); + break; + + case IdentifierConstraintType_Wildcard: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value GLOB ?")); + break; + + case IdentifierConstraintType_Equal: + default: + s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value=?")); + break; + } + + assert(s.get() != NULL); + + s->BindInt(0, level); + s->BindInt(1, tag.GetGroup()); + s->BindInt(2, tag.GetElement()); + s->BindString(3, value); + + target.clear(); + + while (s->Step()) + { + target.push_back(s->ColumnInt64(0)); + } + } + + + void DatabaseWrapperBase::LookupIdentifierRange(std::list<int64_t>& target, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE " + "d.id = r.internalId AND r.resourceType=? AND " + "d.tagGroup=? AND d.tagElement=? AND d.value>=? AND d.value<=?"); + + statement.BindInt(0, level); + statement.BindInt(1, tag.GetGroup()); + statement.BindInt(2, tag.GetElement()); + statement.BindString(3, start); + statement.BindString(4, end); + + target.clear(); + + while (statement.Step()) + { + target.push_back(statement.ColumnInt64(0)); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,210 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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/DicomFormat/DicomMap.h" +#include "../Core/DicomFormat/DicomTag.h" +#include "../Core/Enumerations.h" +#include "../Core/FileStorage/FileInfo.h" +#include "../Core/SQLite/Connection.h" +#include "../OrthancServer/ExportedResource.h" +#include "../OrthancServer/ServerIndexChange.h" +#include "ServerEnumerations.h" + +#include <list> + + +namespace Orthanc +{ + /** + * This class is shared between the Orthanc core and the sample + * database plugin whose code is in + * "../Plugins/Samples/DatabasePlugin". + **/ + class DatabaseWrapperBase + { + private: + SQLite::Connection& db_; + + ErrorCode GetChangesInternal(std::list<ServerIndexChange>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults); + + void GetExportedResourcesInternal(std::list<ExportedResource>& target, + bool& done, + SQLite::Statement& s, + uint32_t maxResults); + + public: + DatabaseWrapperBase(SQLite::Connection& db) : db_(db) + { + } + + void SetGlobalProperty(GlobalProperty property, + const std::string& value); + + bool LookupGlobalProperty(std::string& target, + GlobalProperty property); + + int64_t CreateResource(const std::string& publicId, + ResourceType type); + + bool LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId); + + ErrorCode LookupParent(bool& found, + int64_t& parentId, + int64_t resourceId); + + bool GetPublicId(std::string& result, + int64_t resourceId); + + ErrorCode GetResourceType(ResourceType& result, + int64_t resourceId); + + void AttachChild(int64_t parent, + int64_t child); + + void SetMetadata(int64_t id, + MetadataType type, + const std::string& value); + + void DeleteMetadata(int64_t id, + MetadataType type); + + bool LookupMetadata(std::string& target, + int64_t id, + MetadataType type); + + void ListAvailableMetadata(std::list<MetadataType>& target, + int64_t id); + + void AddAttachment(int64_t id, + const FileInfo& attachment); + + void DeleteAttachment(int64_t id, + FileContentType attachment); + + void ListAvailableAttachments(std::list<FileContentType>& target, + int64_t id); + + bool LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType); + + + void ClearMainDicomTags(int64_t id); + + + void SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value); + + void SetIdentifierTag(int64_t id, + const DicomTag& tag, + const std::string& value); + + void GetMainDicomTags(DicomMap& map, + int64_t id); + + void GetChildrenPublicId(std::list<std::string>& target, + int64_t id); + + void GetChildrenInternalId(std::list<int64_t>& target, + int64_t id); + + void LogChange(int64_t internalId, + const ServerIndexChange& change); + + ErrorCode GetChanges(std::list<ServerIndexChange>& target, + bool& done, + int64_t since, + uint32_t maxResults); + + ErrorCode GetLastChange(std::list<ServerIndexChange>& target); + + void LogExportedResource(const ExportedResource& resource); + + void GetExportedResources(std::list<ExportedResource>& target, + bool& done, + int64_t since, + uint32_t maxResults); + + void GetLastExportedResource(std::list<ExportedResource>& target); + + uint64_t GetTotalCompressedSize(); + + uint64_t GetTotalUncompressedSize(); + + void GetAllInternalIds(std::list<int64_t>& target, + ResourceType resourceType); + + void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType); + + void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit); + + uint64_t GetResourceCount(ResourceType resourceType); + + bool SelectPatientToRecycle(int64_t& internalId); + + bool SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid); + + bool IsProtectedPatient(int64_t internalId); + + void SetProtectedPatient(int64_t internalId, + bool isProtected); + + bool IsExistingResource(int64_t internalId); + + void LookupIdentifier(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + IdentifierConstraintType type, + const std::string& value); + + void LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end); + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/DatabasePluginSample/Plugin.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,102 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "Database.h" + +#include <memory> +#include <iostream> +#include <boost/algorithm/string/predicate.hpp> + +static OrthancPluginContext* context_ = NULL; +static std::auto_ptr<OrthancPlugins::IDatabaseBackend> backend_; + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) + { + context_ = c; + OrthancPluginLogWarning(context_, "Sample plugin is initializing"); + + /* 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; + } + + std::string path = "SampleDatabase.sqlite"; + uint32_t argCount = OrthancPluginGetCommandLineArgumentsCount(context_); + for (uint32_t i = 0; i < argCount; i++) + { + char* tmp = OrthancPluginGetCommandLineArgument(context_, i); + std::string argument(tmp); + OrthancPluginFreeString(context_, tmp); + + if (boost::starts_with(argument, "--database=")) + { + path = argument.substr(11); + } + } + + std::string s = "Using the following SQLite database: " + path; + OrthancPluginLogWarning(context_, s.c_str()); + + backend_.reset(new Database(path)); + OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_); + + return 0; + } + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + backend_.reset(NULL); + } + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "sample-database"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return "1.0"; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/BagOfTasks.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ICommand.h" + +#include <list> +#include <cstddef> + +namespace Orthanc +{ + class BagOfTasks : public boost::noncopyable + { + private: + typedef std::list<ICommand*> Tasks; + + Tasks tasks_; + + public: + ~BagOfTasks() + { + for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it) + { + delete *it; + } + } + + ICommand* Pop() + { + ICommand* task = tasks_.front(); + tasks_.pop_front(); + return task; + } + + void Push(ICommand* task) // Takes ownership + { + if (task != NULL) + { + tasks_.push_back(task); + } + } + + size_t GetSize() const + { + return tasks_.size(); + } + + bool IsEmpty() const + { + return tasks_.empty(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,277 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "BagOfTasksProcessor.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +#include <stdio.h> + +namespace Orthanc +{ + class BagOfTasksProcessor::Task : public IDynamicObject + { + private: + uint64_t bag_; + std::auto_ptr<ICommand> command_; + + public: + Task(uint64_t bag, + ICommand* command) : + bag_(bag), + command_(command) + { + } + + bool Execute() + { + try + { + return command_->Execute(); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What(); + return false; + } + catch (std::runtime_error& e) + { + LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what(); + return false; + } + catch (...) + { + LOG(ERROR) << "Native exception while processing a bag of tasks"; + return false; + } + } + + uint64_t GetBag() + { + return bag_; + } + }; + + + void BagOfTasksProcessor::SignalProgress(Task& task, + Bag& bag) + { + assert(bag.done_ < bag.size_); + + bag.done_ += 1; + + if (bag.done_ == bag.size_) + { + exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running); + bagFinished_.notify_all(); + } + } + + void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that) + { + while (that->continue_) + { + std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100)); + if (obj.get() != NULL) + { + Task& task = *dynamic_cast<Task*>(obj.get()); + + { + boost::mutex::scoped_lock lock(that->mutex_); + + Bags::iterator bag = that->bags_.find(task.GetBag()); + assert(bag != that->bags_.end()); + assert(bag->second.done_ < bag->second.size_); + + if (bag->second.status_ != BagStatus_Running) + { + // Do not execute this task, as its parent bag of tasks + // has failed or is tagged as canceled + that->SignalProgress(task, bag->second); + continue; + } + } + + bool success = task.Execute(); + + { + boost::mutex::scoped_lock lock(that->mutex_); + + Bags::iterator bag = that->bags_.find(task.GetBag()); + assert(bag != that->bags_.end()); + + if (!success) + { + bag->second.status_ = BagStatus_Failed; + } + + that->SignalProgress(task, bag->second); + } + } + } + } + + + void BagOfTasksProcessor::Cancel(int64_t bag) + { + boost::mutex::scoped_lock lock(mutex_); + + Bags::iterator it = bags_.find(bag); + if (it != bags_.end()) + { + it->second.status_ = BagStatus_Canceled; + } + } + + + bool BagOfTasksProcessor::Join(int64_t bag) + { + boost::mutex::scoped_lock lock(mutex_); + + while (continue_) + { + ExitStatus::iterator it = exitStatus_.find(bag); + if (it == exitStatus_.end()) // The bag is still running + { + bagFinished_.wait(lock); + } + else + { + bool status = it->second; + exitStatus_.erase(it); + return status; + } + } + + return false; // The processor is stopping + } + + + float BagOfTasksProcessor::GetProgress(int64_t bag) + { + boost::mutex::scoped_lock lock(mutex_); + + Bags::const_iterator it = bags_.find(bag); + if (it == bags_.end()) + { + // The bag of tasks has finished + return 1.0f; + } + else + { + return (static_cast<float>(it->second.done_) / + static_cast<float>(it->second.size_)); + } + } + + + bool BagOfTasksProcessor::Handle::Join() + { + if (hasJoined_) + { + return status_; + } + else + { + status_ = that_.Join(bag_); + hasJoined_ = true; + return status_; + } + } + + + BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : + countBags_(0), + continue_(true) + { + if (countThreads == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + threads_.resize(countThreads); + + for (size_t i = 0; i < threads_.size(); i++) + { + threads_[i] = new boost::thread(Worker, this); + } + } + + + BagOfTasksProcessor::~BagOfTasksProcessor() + { + continue_ = false; + + bagFinished_.notify_all(); // Wakes up all the pending "Join()" + + for (size_t i = 0; i < threads_.size(); i++) + { + if (threads_[i]) + { + if (threads_[i]->joinable()) + { + threads_[i]->join(); + } + + delete threads_[i]; + threads_[i] = NULL; + } + } + } + + + BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks) + { + if (tasks.GetSize() == 0) + { + return new Handle(*this, 0, true); + } + + boost::mutex::scoped_lock lock(mutex_); + + uint64_t id = countBags_; + countBags_ += 1; + + Bag bag(tasks.GetSize()); + bags_[id] = bag; + + while (!tasks.IsEmpty()) + { + queue_.Enqueue(new Task(id, tasks.Pop())); + } + + return new Handle(*this, id, false); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,150 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "BagOfTasks.h" +#include "SharedMessageQueue.h" + +#include <stdint.h> +#include <map> + +namespace Orthanc +{ + class BagOfTasksProcessor : public boost::noncopyable + { + private: + enum BagStatus + { + BagStatus_Running, + BagStatus_Canceled, + BagStatus_Failed + }; + + + struct Bag + { + size_t size_; + size_t done_; + BagStatus status_; + + Bag() : + size_(0), + done_(0), + status_(BagStatus_Failed) + { + } + + explicit Bag(size_t size) : + size_(size), + done_(0), + status_(BagStatus_Running) + { + } + }; + + class Task; + + + typedef std::map<uint64_t, Bag> Bags; + typedef std::map<uint64_t, bool> ExitStatus; + + SharedMessageQueue queue_; + + boost::mutex mutex_; + uint64_t countBags_; + Bags bags_; + std::vector<boost::thread*> threads_; + ExitStatus exitStatus_; + bool continue_; + + boost::condition_variable bagFinished_; + + static void Worker(BagOfTasksProcessor* that); + + void Cancel(int64_t bag); + + bool Join(int64_t bag); + + float GetProgress(int64_t bag); + + void SignalProgress(Task& task, + Bag& bag); + + public: + class Handle : public boost::noncopyable + { + friend class BagOfTasksProcessor; + + private: + BagOfTasksProcessor& that_; + uint64_t bag_; + bool hasJoined_; + bool status_; + + Handle(BagOfTasksProcessor& that, + uint64_t bag, + bool empty) : + that_(that), + bag_(bag), + hasJoined_(empty) + { + } + + public: + ~Handle() + { + Join(); + } + + void Cancel() + { + that_.Cancel(bag_); + } + + bool Join(); + + float GetProgress() + { + return that_.GetProgress(bag_); + } + }; + + + explicit BagOfTasksProcessor(size_t countThreads); + + ~BagOfTasksProcessor(); + + Handle* Submit(BagOfTasks& tasks); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/ICommand.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IDynamicObject.h" + +namespace Orthanc +{ + /** + * This class is the base class for the "Command" design pattern. + * http://en.wikipedia.org/wiki/Command_pattern + **/ + class ICommand : public IDynamicObject + { + public: + virtual bool Execute() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/ILockable.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 ILockable : public boost::noncopyable + { + friend class Locker; + + protected: + virtual void Lock() = 0; + + virtual void Unlock() = 0; + + public: + virtual ~ILockable() + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Locker.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ILockable.h" + +namespace Orthanc +{ + class Locker : public boost::noncopyable + { + private: + ILockable& lockable_; + + public: + Locker(ILockable& lockable) : lockable_(lockable) + { + lockable_.Lock(); + } + + virtual ~Locker() + { + lockable_.Unlock(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Mutex.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,122 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "Mutex.h" + +#include "../OrthancException.h" + +#if defined(_WIN32) +#include <windows.h> +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#include <pthread.h> +#else +#error Support your platform here +#endif + +namespace Orthanc +{ +#if defined (_WIN32) + + struct Mutex::PImpl + { + CRITICAL_SECTION criticalSection_; + }; + + Mutex::Mutex() + { + pimpl_ = new PImpl; + ::InitializeCriticalSection(&pimpl_->criticalSection_); + } + + Mutex::~Mutex() + { + ::DeleteCriticalSection(&pimpl_->criticalSection_); + delete pimpl_; + } + + void Mutex::Lock() + { + ::EnterCriticalSection(&pimpl_->criticalSection_); + } + + void Mutex::Unlock() + { + ::LeaveCriticalSection(&pimpl_->criticalSection_); + } + + +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + struct Mutex::PImpl + { + pthread_mutex_t mutex_; + }; + + Mutex::Mutex() + { + pimpl_ = new PImpl; + + if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0) + { + delete pimpl_; + throw OrthancException(ErrorCode_InternalError); + } + } + + Mutex::~Mutex() + { + pthread_mutex_destroy(&pimpl_->mutex_); + delete pimpl_; + } + + void Mutex::Lock() + { + if (pthread_mutex_lock(&pimpl_->mutex_) != 0) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + void Mutex::Unlock() + { + if (pthread_mutex_unlock(&pimpl_->mutex_) != 0) + { + throw OrthancException(ErrorCode_InternalError); + } + } + +#else +#error Support your plateform here +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/Mutex.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ILockable.h" + +namespace Orthanc +{ + class Mutex : public ILockable + { + private: + struct PImpl; + + PImpl *pimpl_; + + protected: + virtual void Lock(); + + virtual void Unlock(); + + public: + Mutex(); + + ~Mutex(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,126 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ReaderWriterLock.h" + +#include <boost/thread/shared_mutex.hpp> + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation + // modules. + + class ReaderLockable : public ILockable + { + private: + boost::shared_mutex& lock_; + + protected: + virtual void Lock() + { + lock_.lock_shared(); + } + + virtual void Unlock() + { + lock_.unlock_shared(); + } + + public: + explicit ReaderLockable(boost::shared_mutex& lock) : lock_(lock) + { + } + }; + + + class WriterLockable : public ILockable + { + private: + boost::shared_mutex& lock_; + + protected: + virtual void Lock() + { + lock_.lock(); + } + + virtual void Unlock() + { + lock_.unlock(); + } + + public: + explicit WriterLockable(boost::shared_mutex& lock) : lock_(lock) + { + } + }; + } + + struct ReaderWriterLock::PImpl + { + boost::shared_mutex lock_; + ReaderLockable reader_; + WriterLockable writer_; + + PImpl() : reader_(lock_), writer_(lock_) + { + } + }; + + + ReaderWriterLock::ReaderWriterLock() + { + pimpl_ = new PImpl; + } + + + ReaderWriterLock::~ReaderWriterLock() + { + delete pimpl_; + } + + + ILockable& ReaderWriterLock::ForReader() + { + return pimpl_->reader_; + } + + + ILockable& ReaderWriterLock::ForWriter() + { + return pimpl_->writer_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ILockable.h" + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ReaderWriterLock : public boost::noncopyable + { + private: + struct PImpl; + + PImpl *pimpl_; + + public: + ReaderWriterLock(); + + virtual ~ReaderWriterLock(); + + ILockable& ForReader(); + + ILockable& ForWriter(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "CallSystemCommand.h" + +#include "../../Core/Logging.h" +#include "../../Core/Toolbox.h" +#include "../../Core/TemporaryFile.h" + +namespace Orthanc +{ + CallSystemCommand::CallSystemCommand(ServerContext& context, + const std::string& command, + const std::vector<std::string>& arguments) : + context_(context), + command_(command), + arguments_(arguments) + { + } + + bool CallSystemCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Calling system command " << command_ << " on instance " << *it; + + try + { + std::string dicom; + context_.ReadDicom(dicom, *it); + + TemporaryFile tmp; + tmp.Write(dicom); + + std::vector<std::string> args = arguments_; + args.push_back(tmp.GetPath()); + + SystemToolbox::ExecuteSystemCommand(command_, args); + + // Only chain with other commands if this command succeeds + outputs.push_back(*it); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to call system command " << command_ + << " on instance " << *it << " in a Lua script: " << e.What(); + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,56 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IServerCommand.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class CallSystemCommand : public IServerCommand + { + private: + ServerContext& context_; + std::string command_; + std::vector<std::string> arguments_; + + public: + CallSystemCommand(ServerContext& context, + const std::string& command, + const std::vector<std::string>& arguments); + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,62 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DeleteInstanceCommand.h" + +#include "../../Core/Logging.h" + +namespace Orthanc +{ + bool DeleteInstanceCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Deleting instance " << *it; + + try + { + Json::Value tmp; + context_.DeleteResource(tmp, *it, ResourceType_Instance); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to delete instance " << *it << ": " << e.What(); + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IServerCommand.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class DeleteInstanceCommand : public IServerCommand + { + private: + ServerContext& context_; + + public: + DeleteInstanceCommand(ServerContext& context) : context_(context) + { + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/IServerCommand.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <list> +#include <string> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class IServerCommand : public boost::noncopyable + { + public: + typedef std::list<std::string> ListOfStrings; + + virtual ~IServerCommand() + { + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "ModifyInstanceCommand.h" + +#include "../../Core/Logging.h" + +namespace Orthanc +{ + ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, + RequestOrigin origin, + DicomModification* modification) : + context_(context), + origin_(origin), + modification_(modification) + { + modification_->SetAllowManualIdentifiers(true); + + if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) + { + modification_->SetLevel(ResourceType_Patient); + } + else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Study); + } + else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + modification_->SetLevel(ResourceType_Series); + } + else + { + modification_->SetLevel(ResourceType_Instance); + } + + if (origin_ != RequestOrigin_Lua) + { + // TODO If issued from HTTP, "remoteIp" and "username" must be provided + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + ModifyInstanceCommand::~ModifyInstanceCommand() + { + if (modification_) + { + delete modification_; + } + } + + + bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Modifying resource " << *it; + + try + { + std::auto_ptr<ParsedDicomFile> modified; + + { + ServerContext::DicomCacheLocker lock(context_, *it); + modified.reset(lock.GetDicom().Clone(true)); + } + + modification_->Apply(*modified); + + DicomInstanceToStore toStore; + assert(origin_ == RequestOrigin_Lua); + toStore.SetLuaOrigin(); + toStore.SetParsedDicomFile(*modified); + // TODO other metadata + toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it); + + std::string modifiedId; + context_.Store(modifiedId, toStore); + + // Only chain with other commands if this command succeeds + outputs.push_back(modifiedId); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to modify instance " << *it << ": " << e.What(); + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IServerCommand.h" +#include "../ServerContext.h" +#include "../../Core/DicomParsing/DicomModification.h" + +namespace Orthanc +{ + class ModifyInstanceCommand : public IServerCommand + { + private: + ServerContext& context_; + RequestOrigin origin_; + DicomModification* modification_; + + public: + ModifyInstanceCommand(ServerContext& context, + RequestOrigin origin, + DicomModification* modification); // takes the ownership + + virtual ~ModifyInstanceCommand(); + + const DicomModification& GetModification() const + { + return *modification_; + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,188 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ReusableDicomUserConnection.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + static boost::posix_time::ptime Now() + { + return boost::posix_time::microsec_clock::local_time(); + } + + void ReusableDicomUserConnection::Open(const std::string& localAet, + const RemoteModalityParameters& remote) + { + if (connection_ != NULL && + connection_->GetLocalApplicationEntityTitle() == localAet && + connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && + connection_->GetRemoteHost() == remote.GetHost() && + connection_->GetRemotePort() == remote.GetPort() && + connection_->GetRemoteManufacturer() == remote.GetManufacturer()) + { + // The current connection can be reused + LOG(INFO) << "Reusing the previous SCU connection"; + return; + } + + Close(); + + connection_ = new DicomUserConnection(); + connection_->SetLocalApplicationEntityTitle(localAet); + connection_->SetRemoteModality(remote); + connection_->Open(); + } + + void ReusableDicomUserConnection::Close() + { + if (connection_ != NULL) + { + delete connection_; + connection_ = NULL; + } + } + + void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) + { + for (;;) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + if (!that->continue_) + { + //LOG(INFO) << "Finishing the thread watching the global SCU connection"; + return; + } + + { + boost::mutex::scoped_lock lock(that->mutex_); + if (that->connection_ != NULL && + Now() >= that->lastUse_ + that->timeBeforeClose_) + { + LOG(INFO) << "Closing the global SCU connection after timeout"; + that->Close(); + } + } + } + } + + + ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, + const std::string& localAet, + const RemoteModalityParameters& remote) : + ::Orthanc::Locker(that) + { + that.Open(localAet, remote); + connection_ = that.connection_; + } + + + DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() + { + if (connection_ == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *connection_; + } + + ReusableDicomUserConnection::ReusableDicomUserConnection() : + connection_(NULL), + timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds + { + lastUse_ = Now(); + continue_ = true; + closeThread_ = boost::thread(CloseThread, this); + } + + ReusableDicomUserConnection::~ReusableDicomUserConnection() + { + if (continue_) + { + LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Finalize(); + } + } + + void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) + { + boost::mutex::scoped_lock lock(mutex_); + + if (ms == 0) + { + ms = 1; + } + + timeBeforeClose_ = boost::posix_time::milliseconds(ms); + } + + void ReusableDicomUserConnection::Lock() + { + mutex_.lock(); + } + + void ReusableDicomUserConnection::Unlock() + { + if (connection_ != NULL && + connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) + { + // "storescp" from DCMTK has problems when reusing a + // connection. Always close. + Close(); + } + + lastUse_ = Now(); + mutex_.unlock(); + } + + + void ReusableDicomUserConnection::Finalize() + { + if (continue_) + { + continue_ = false; + + if (closeThread_.joinable()) + { + closeThread_.join(); + } + + Close(); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomUserConnection.h" +#include "../../Core/MultiThreading/Locker.h" + +#include <boost/thread.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace Orthanc +{ + class ReusableDicomUserConnection : public ILockable + { + private: + boost::mutex mutex_; + DicomUserConnection* connection_; + bool continue_; + boost::posix_time::time_duration timeBeforeClose_; + boost::posix_time::ptime lastUse_; + boost::thread closeThread_; + + void Open(const std::string& localAet, + const RemoteModalityParameters& remote); + + void Close(); + + static void CloseThread(ReusableDicomUserConnection* that); + + protected: + virtual void Lock(); + + virtual void Unlock(); + + public: + class Locker : public ::Orthanc::Locker + { + private: + DicomUserConnection* connection_; + + public: + Locker(ReusableDicomUserConnection& that, + const std::string& localAet, + const RemoteModalityParameters& remote); + + DicomUserConnection& GetConnection(); + }; + + ReusableDicomUserConnection(); + + virtual ~ReusableDicomUserConnection(); + + void SetMillisecondsBeforeClose(uint64_t ms); + + void Finalize(); + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ServerCommandInstance.h" + +#include "../../Core/OrthancException.h" + +namespace Orthanc +{ + bool ServerCommandInstance::Execute(IListener& listener) + { + ListOfStrings outputs; + + bool success = false; + + try + { + if (command_->Apply(outputs, inputs_)) + { + success = true; + } + } + catch (OrthancException&) + { + } + + if (!success) + { + listener.SignalFailure(jobId_); + return true; + } + + for (std::list<ServerCommandInstance*>::iterator + it = next_.begin(); it != next_.end(); ++it) + { + for (ListOfStrings::const_iterator + output = outputs.begin(); output != outputs.end(); ++output) + { + (*it)->AddInput(*output); + } + } + + listener.SignalSuccess(jobId_); + return true; + } + + + ServerCommandInstance::ServerCommandInstance(IServerCommand *command, + const std::string& jobId) : + command_(command), + jobId_(jobId), + connectedToSink_(false) + { + if (command_ == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + ServerCommandInstance::~ServerCommandInstance() + { + if (command_ != NULL) + { + delete command_; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.h Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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/IDynamicObject.h" +#include "IServerCommand.h" + +namespace Orthanc +{ + class ServerCommandInstance : public IDynamicObject + { + friend class ServerScheduler; + + public: + class IListener + { + public: + virtual ~IListener() + { + } + + virtual void SignalSuccess(const std::string& jobId) = 0; + + virtual void SignalFailure(const std::string& jobId) = 0; + }; + + private: + typedef IServerCommand::ListOfStrings ListOfStrings; + + IServerCommand *command_; + std::string jobId_; + ListOfStrings inputs_; + std::list<ServerCommandInstance*> next_; + bool connectedToSink_; + + bool Execute(IListener& listener); + + public: + ServerCommandInstance(IServerCommand *command, + const std::string& jobId); + + virtual ~ServerCommandInstance(); + + const std::string& GetJobId() const + { + return jobId_; + } + + void AddInput(const std::string& input) + { + inputs_.push_back(input); + } + + void ConnectOutput(ServerCommandInstance& next) + { + next_.push_back(&next); + } + + void SetConnectedToSink(bool connected = true) + { + connectedToSink_ = connected; + } + + bool IsConnectedToSink() const + { + return connectedToSink_; + } + + const std::list<ServerCommandInstance*>& GetNextCommands() const + { + return next_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerJob.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -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-2018 Osimis S.A., 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 "ServerJob.h" + +#include "../../Core/OrthancException.h" +#include "../../Core/Toolbox.h" + +namespace Orthanc +{ + void ServerJob::CheckOrdering() + { + std::map<ServerCommandInstance*, unsigned int> index; + + unsigned int count = 0; + for (std::list<ServerCommandInstance*>::const_iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + index[*it] = count++; + } + + for (std::list<ServerCommandInstance*>::const_iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands(); + + for (std::list<ServerCommandInstance*>::const_iterator + next = nextCommands.begin(); next != nextCommands.end(); ++next) + { + if (index.find(*next) == index.end() || + index[*next] <= index[*it]) + { + // You must reorder your calls to "ServerJob::AddCommand" + throw OrthancException(ErrorCode_BadJobOrdering); + } + } + } + } + + + size_t ServerJob::Submit(SharedMessageQueue& target, + ServerCommandInstance::IListener& listener) + { + if (submitted_) + { + // This job has already been submitted + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + CheckOrdering(); + + size_t size = filters_.size(); + + for (std::list<ServerCommandInstance*>::iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + target.Enqueue(*it); + } + + filters_.clear(); + submitted_ = true; + + return size; + } + + + ServerJob::ServerJob() : + jobId_(Toolbox::GenerateUuid()), + submitted_(false), + description_("no description") + { + } + + + ServerJob::~ServerJob() + { + for (std::list<ServerCommandInstance*>::iterator + it = filters_.begin(); it != filters_.end(); ++it) + { + delete *it; + } + + for (std::list<IDynamicObject*>::iterator + it = payloads_.begin(); it != payloads_.end(); ++it) + { + delete *it; + } + } + + + ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter) + { + if (submitted_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + filters_.push_back(new ServerCommandInstance(filter, jobId_)); + + return *filters_.back(); + } + + + IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload) + { + if (submitted_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + payloads_.push_back(payload); + + return *filters_.back(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerJob.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ServerCommandInstance.h" +#include "../../Core/MultiThreading/SharedMessageQueue.h" + +namespace Orthanc +{ + class ServerJob + { + friend class ServerScheduler; + + private: + std::list<ServerCommandInstance*> filters_; + std::list<IDynamicObject*> payloads_; + std::string jobId_; + bool submitted_; + std::string description_; + + void CheckOrdering(); + + size_t Submit(SharedMessageQueue& target, + ServerCommandInstance::IListener& listener); + + public: + ServerJob(); + + ~ServerJob(); + + const std::string& GetId() const + { + return jobId_; + } + + void SetDescription(const std::string& description) + { + description_ = description; + } + + const std::string& GetDescription() const + { + return description_; + } + + ServerCommandInstance& AddCommand(IServerCommand* filter); + + // Take the ownership of a payload to a job. This payload will be + // automatically freed when the job succeeds or fails. + IDynamicObject& AddPayload(IDynamicObject* payload); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerScheduler.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,359 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ServerScheduler.h" + +#include "../../Core/OrthancException.h" +#include "../../Core/Logging.h" + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + class Sink : public IServerCommand + { + private: + ListOfStrings& target_; + + public: + explicit Sink(ListOfStrings& target) : target_(target) + { + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + target_.push_back(*it); + } + + return true; + } + }; + } + + + ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId) + { + Jobs::iterator info = jobs_.find(jobId); + + if (info == jobs_.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + + return info->second; + } + + + void ServerScheduler::SignalSuccess(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + JobInfo& info = GetJobInfo(jobId); + info.success_++; + + assert(info.failures_ == 0); + + if (info.success_ >= info.size_) + { + if (info.watched_) + { + watchedJobStatus_[jobId] = JobStatus_Success; + watchedJobFinished_.notify_all(); + } + + LOG(INFO) << "Job successfully finished (" << info.description_ << ")"; + jobs_.erase(jobId); + + availableJob_.Release(); + } + } + + + void ServerScheduler::SignalFailure(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + JobInfo& info = GetJobInfo(jobId); + info.failures_++; + + if (info.success_ + info.failures_ >= info.size_) + { + if (info.watched_) + { + watchedJobStatus_[jobId] = JobStatus_Failure; + watchedJobFinished_.notify_all(); + } + + LOG(ERROR) << "Job has failed (" << info.description_ << ")"; + jobs_.erase(jobId); + + availableJob_.Release(); + } + } + + + void ServerScheduler::Worker(ServerScheduler* that) + { + static const int32_t TIMEOUT = 100; + + LOG(WARNING) << "The server scheduler has started"; + + while (!that->finish_) + { + std::auto_ptr<IDynamicObject> object(that->queue_.Dequeue(TIMEOUT)); + if (object.get() != NULL) + { + ServerCommandInstance& filter = dynamic_cast<ServerCommandInstance&>(*object); + + // Skip the execution of this filter if its parent job has + // previously failed. + bool jobHasFailed; + { + boost::mutex::scoped_lock lock(that->mutex_); + JobInfo& info = that->GetJobInfo(filter.GetJobId()); + jobHasFailed = (info.failures_ > 0 || info.cancel_); + } + + if (jobHasFailed) + { + that->SignalFailure(filter.GetJobId()); + } + else + { + filter.Execute(*that); + } + } + } + } + + + void ServerScheduler::SubmitInternal(ServerJob& job, + bool watched) + { + availableJob_.Acquire(); + + boost::mutex::scoped_lock lock(mutex_); + + JobInfo info; + info.size_ = job.Submit(queue_, *this); + info.cancel_ = false; + info.success_ = 0; + info.failures_ = 0; + info.description_ = job.GetDescription(); + info.watched_ = watched; + + assert(info.size_ > 0); + + if (watched) + { + watchedJobStatus_[job.GetId()] = JobStatus_Running; + } + + jobs_[job.GetId()] = info; + + LOG(INFO) << "New job submitted (" << job.description_ << ")"; + } + + + ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs) + { + if (maxJobs == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + finish_ = false; + worker_ = boost::thread(Worker, this); + } + + + ServerScheduler::~ServerScheduler() + { + if (!finish_) + { + LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + + void ServerScheduler::Stop() + { + if (!finish_) + { + finish_ = true; + + if (worker_.joinable()) + { + worker_.join(); + } + } + } + + + void ServerScheduler::Submit(ServerJob& job) + { + if (job.filters_.empty()) + { + return; + } + + SubmitInternal(job, false); + } + + + bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs, + ServerJob& job) + { + std::string jobId = job.GetId(); + + outputs.clear(); + + if (job.filters_.empty()) + { + return true; + } + + // Add a sink filter to collect all the results of the filters + // that have no next filter. + ServerCommandInstance& sink = job.AddCommand(new Sink(outputs)); + + for (std::list<ServerCommandInstance*>::iterator + it = job.filters_.begin(); it != job.filters_.end(); ++it) + { + if ((*it) != &sink && + (*it)->IsConnectedToSink()) + { + (*it)->ConnectOutput(sink); + } + } + + // Submit the job + SubmitInternal(job, true); + + // Wait for the job to complete (either success or failure) + JobStatus status; + + { + boost::mutex::scoped_lock lock(mutex_); + + assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end()); + + while (watchedJobStatus_[jobId] == JobStatus_Running) + { + watchedJobFinished_.wait(lock); + } + + status = watchedJobStatus_[jobId]; + watchedJobStatus_.erase(jobId); + } + + return (status == JobStatus_Success); + } + + + bool ServerScheduler::SubmitAndWait(ServerJob& job) + { + ListOfStrings ignoredSink; + return SubmitAndWait(ignoredSink, job); + } + + + bool ServerScheduler::IsRunning(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + return jobs_.find(jobId) != jobs_.end(); + } + + + void ServerScheduler::Cancel(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + Jobs::iterator job = jobs_.find(jobId); + + if (job != jobs_.end()) + { + job->second.cancel_ = true; + LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")"; + } + } + + + float ServerScheduler::GetProgress(const std::string& jobId) + { + boost::mutex::scoped_lock lock(mutex_); + + Jobs::iterator job = jobs_.find(jobId); + + if (job == jobs_.end() || + job->second.size_ == 0 /* should never happen */) + { + // This job is not running + return 1; + } + + if (job->second.failures_ != 0) + { + return 1; + } + + if (job->second.size_ == 1) + { + return static_cast<float>(job->second.success_); + } + + return (static_cast<float>(job->second.success_) / + static_cast<float>(job->second.size_ - 1)); + } + + + void ServerScheduler::GetListOfJobs(ListOfStrings& jobs) + { + boost::mutex::scoped_lock lock(mutex_); + + jobs.clear(); + + for (Jobs::const_iterator + it = jobs_.begin(); it != jobs_.end(); ++it) + { + jobs.push_back(it->first); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/ServerScheduler.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,123 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ServerJob.h" + +#include "../../Core/MultiThreading/Semaphore.h" + +namespace Orthanc +{ + class ServerScheduler : public ServerCommandInstance::IListener + { + private: + struct JobInfo + { + bool watched_; + bool cancel_; + size_t size_; + size_t success_; + size_t failures_; + std::string description_; + }; + + enum JobStatus + { + JobStatus_Running = 1, + JobStatus_Success = 2, + JobStatus_Failure = 3 + }; + + typedef IServerCommand::ListOfStrings ListOfStrings; + typedef std::map<std::string, JobInfo> Jobs; + + boost::mutex mutex_; + boost::condition_variable watchedJobFinished_; + Jobs jobs_; + SharedMessageQueue queue_; + bool finish_; + boost::thread worker_; + std::map<std::string, JobStatus> watchedJobStatus_; + Semaphore availableJob_; + + JobInfo& GetJobInfo(const std::string& jobId); + + virtual void SignalSuccess(const std::string& jobId); + + virtual void SignalFailure(const std::string& jobId); + + static void Worker(ServerScheduler* that); + + void SubmitInternal(ServerJob& job, + bool watched); + + public: + explicit ServerScheduler(unsigned int maxjobs); + + ~ServerScheduler(); + + void Stop(); + + void Submit(ServerJob& job); + + bool SubmitAndWait(ListOfStrings& outputs, + ServerJob& job); + + bool SubmitAndWait(ServerJob& job); + + bool IsRunning(const std::string& jobId); + + void Cancel(const std::string& jobId); + + // Returns a number between 0 and 1 + float GetProgress(const std::string& jobId); + + bool IsRunning(const ServerJob& job) + { + return IsRunning(job.GetId()); + } + + void Cancel(const ServerJob& job) + { + Cancel(job.GetId()); + } + + float GetProgress(const ServerJob& job) + { + return GetProgress(job.GetId()); + } + + void GetListOfJobs(ListOfStrings& jobs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,92 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "StorePeerCommand.h" + +#include "../../Core/Logging.h" +#include "../../Core/HttpClient.h" + +namespace Orthanc +{ + StorePeerCommand::StorePeerCommand(ServerContext& context, + const WebServiceParameters& peer, + bool ignoreExceptions) : + context_(context), + peer_(peer), + ignoreExceptions_(ignoreExceptions) + { + } + + bool StorePeerCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + // Configure the HTTP client + HttpClient client(peer_, "instances"); + client.SetMethod(HttpMethod_Post); + + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to peer \"" + << peer_.GetUrl() << "\""; + + try + { + context_.ReadDicom(client.GetBody(), *it); + + std::string answer; + if (!client.Apply(answer)) + { + LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\""; + throw OrthancException(ErrorCode_NetworkProtocol); + } + + // Only chain with other commands if this command succeeds + outputs.push_back(*it); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Unable to forward to an Orthanc peer in (instance " + << *it << ", peer " << peer_.GetUrl() << "): " << e.What(); + + if (!ignoreExceptions_) + { + throw; + } + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IServerCommand.h" +#include "../ServerContext.h" +#include "../OrthancInitialization.h" + +namespace Orthanc +{ + class StorePeerCommand : public IServerCommand + { + private: + ServerContext& context_; + WebServiceParameters peer_; + bool ignoreExceptions_; + + public: + StorePeerCommand(ServerContext& context, + const WebServiceParameters& peer, + bool ignoreExceptions); + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,99 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "StoreScuCommand.h" + +#include "../../Core/Logging.h" + +namespace Orthanc +{ + StoreScuCommand::StoreScuCommand(ServerContext& context, + const std::string& localAet, + const RemoteModalityParameters& modality, + bool ignoreExceptions) : + context_(context), + modality_(modality), + ignoreExceptions_(ignoreExceptions), + localAet_(localAet), + moveOriginatorID_(0) + { + } + + + void StoreScuCommand::SetMoveOriginator(const std::string& aet, + uint16_t id) + { + moveOriginatorAET_ = aet; + moveOriginatorID_ = id; + } + + + bool StoreScuCommand::Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); + + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to modality \"" + << modality_.GetApplicationEntityTitle() << "\""; + + try + { + std::string dicom; + context_.ReadDicom(dicom, *it); + + locker.GetConnection().Store(dicom, moveOriginatorAET_, moveOriginatorID_); + + // Only chain with other commands if this command succeeds + outputs.push_back(*it); + } + catch (OrthancException& e) + { + // Ignore transmission errors (e.g. if the remote modality is + // powered off) + LOG(ERROR) << "Unable to forward to a modality in (instance " + << *it << "): " << e.What(); + + if (!ignoreExceptions_) + { + throw; + } + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,63 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "IServerCommand.h" +#include "../ServerContext.h" + +namespace Orthanc +{ + class StoreScuCommand : public IServerCommand + { + private: + ServerContext& context_; + RemoteModalityParameters modality_; + bool ignoreExceptions_; + std::string localAet_; + std::string moveOriginatorAET_; + uint16_t moveOriginatorID_; + + public: + StoreScuCommand(ServerContext& context, + const std::string& localAet, + const RemoteModalityParameters& modality, + bool ignoreExceptions); + + void SetMoveOriginator(const std::string& aet, + uint16_t id); + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/SetupAnonymization2011.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,258 @@ + /** + * This is a manual implementation by Alain Mazy. Only kept for reference. + * https://bitbucket.org/sjodogne/orthanc/commits/c6defdc4c611fca2ab528ba2c6937a742e0329a8?at=issue-46-anonymization + **/ + + void DicomModification::SetupAnonymization2011() + { + // This is Table E.1-1 from PS 3.15-2011 - DICOM Part 15: Security and System Management Profiles + // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2011/11_15pu.pdf + + removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID + removals_.insert(DicomTag(0x0000, 0x1001)); // Requested SOP Instance UID + removals_.insert(DicomTag(0x0002, 0x0003)); // Media Storage SOP Instance UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances + removals_.insert(DicomTag(0x0004, 0x1511)); // Referenced SOP Instance UID in File + removals_.insert(DicomTag(0x0008, 0x0010)); // Irradiation Event UID + removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() + clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date + clearings_.insert(DicomTag(0x0008, 0x0021)); // Series Date + clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time + clearings_.insert(DicomTag(0x0008, 0x0031)); // Series Time + removals_.insert(DicomTag(0x0008, 0x0022)); // Acquisition Date + removals_.insert(DicomTag(0x0008, 0x0023)); // Content Date + removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date + removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date + removals_.insert(DicomTag(0x0008, 0x002a)); // Acquisition DateTime + removals_.insert(DicomTag(0x0008, 0x0032)); // Acquisition Time + removals_.insert(DicomTag(0x0008, 0x0033)); // Content Time + removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time + removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time + removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number + removals_.insert(DicomTag(0x0008, 0x0058)); // Failed SOP Instance UID List + removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name + removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals_.insert(DicomTag(0x0008, 0x0082)); // Institution Code Sequence + removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician's Identification Sequence + removals_.insert(DicomTag(0x0008, 0x010d)); // Context Group Extension Creator UID + removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + removals_.insert(DicomTag(0x0008, 0x0300)); // Current Patient Location + removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name + removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physicians Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals_.insert(DicomTag(0x0008, 0x1062)); // Physician Reading Study Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + removals_.insert(DicomTag(0x0008, 0x1072)); // Operators' Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence + removals_.insert(DicomTag(0x0008, 0x1110)); // Referenced Study Sequence + removals_.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence + removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence + removals_.insert(DicomTag(0x0008, 0x1140)); // Referenced Image Sequence + removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID + removals_.insert(DicomTag(0x0008, 0x1195)); // Transaction UID + removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + removals_.insert(DicomTag(0x0008, 0x2112)); // Source Image Sequence + removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments + removals_.insert(DicomTag(0x0008, 0x9123)); // Creator Version UID + //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) + //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) + removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence + removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence + removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence + removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids + removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence + removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name + removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address + removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification + removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name + removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank + removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service + removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies + removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence + removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence + removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers + removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status + removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status + removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date + removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference + removals_.insert(DicomTag(0x0010, 0x2203)); // Patient's Sex Neutered + removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person + removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization + removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals_.insert(DicomTag(0x0018, 0x0010)); // Contrast Bolus Agent + removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number + removals_.insert(DicomTag(0x0018, 0x1002)); // Device UID + removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID + removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID + removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID + removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID + removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + removals_.insert(DicomTag(0x0018, 0x1400)); // Acquisition Device Processing Description + removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments + removals_.insert(DicomTag(0x0018, 0x700a)); // Detector ID + removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description + removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description + //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() + //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() + removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID + removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID + removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer + removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description + removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments + removals_.insert(DicomTag(0x0020, 0x9161)); // Concatenation UID + removals_.insert(DicomTag(0x0020, 0x9164)); // Dimension Organization UID + //removals_.insert(DicomTag(0x0028, 0x1199)); // Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances + //removals_.insert(DicomTag(0x0028, 0x1214)); // Large Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances + removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments + removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer + removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location + removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title + removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study + removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service + removals_.insert(DicomTag(0x0032, 0x1060)); // Requesting Procedure Description + removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent + removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments + removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID + removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID + removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence + removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date + removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time + removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description + removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs + removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description + removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence + removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State + removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments + removals_.insert(DicomTag(0x0038, 0x1234)); // Referenced Patient Alias Sequence + removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title + removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date + removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time + removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name + removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence + removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name + removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location + removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication + removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title + removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name + removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location + removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0248)); // Performed Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on Performed Procedure Step + removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence + removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID + removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results + removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipient of Results Identification Sequence + removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements + removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location + removals_.insert(DicomTag(0x0040, 0x1101)); // Person Identification Code Sequence + removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address + removals_.insert(DicomTag(0x0040, 0x1103)); // Person Telephone Numbers + removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments + removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By + removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location + removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number + removals_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number of Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number of Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments + removals_.insert(DicomTag(0x0040, 0x4023)); // Referenced General Purpose Scheduled Procedure Step Transaction UID + removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization + removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name + removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization + removals_.insert(DicomTag(0x0040, 0xa073)); // Verifying Observer Sequence + removals_.insert(DicomTag(0x0040, 0xa075)); // Verifying Observer Name + removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence + removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence + removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence + removals_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence + removals_.insert(DicomTag(0x0040, 0xa123)); // Person Name + removals_.insert(DicomTag(0x0040, 0xa124)); // UID + removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description + removals_.insert(DicomTag(0x0040, 0xdb0c)); // Template Extension Organization UID + removals_.insert(DicomTag(0x0040, 0xdb0d)); // Template Extension Creator UID + removals_.insert(DicomTag(0x0070, 0x0001)); // Graphic Annotation Sequence + removals_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name + removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence + removals_.insert(DicomTag(0x0070, 0x031a)); // Fiducial UID + removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID + removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence + removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title + removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject + removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author + removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Key Words + removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID + removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence + removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence + removals_.insert(DicomTag(0x0400, 0x0404)); // MAC + removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence + removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + removals_.insert(DicomTag(0x2030, 0x0020)); // Text String + removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID + removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID + removals_.insert(DicomTag(0x300a, 0x0013)); // Dose Reference UID + removals_.insert(DicomTag(0x300e, 0x0008)); // Reviewer Name + removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary + removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments + removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer + removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder + removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber + removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text + removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author + removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence + removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation + removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description + removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence + removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name + removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address + removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer + removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions + removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments + removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signature Sequence + removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding + //removals_.insert(DicomTag(0x60xx, 0x4000)); // Overlay Comments => TODO + //removals_.insert(DicomTag(0x60xx, 0x3000)); // Overlay Data => TODO + + // Set the DeidentificationMethod tag + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2011); + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ImplementationNotes/JobsEngineStates.dot Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,28 @@ +// dot -Tpdf JobsEngineStates.dot -o JobsEngineStates.pdf + +digraph G +{ + rankdir="LR"; + init [shape=point]; + failure, success [shape=doublecircle]; + + // Internal transitions + init -> pending; + pending -> running; + running -> success; + running -> failure; + running -> retry; + retry -> pending [label="timeout"]; + + // External actions + failure -> pending [label="Resubmit()" fontcolor="red"]; + paused -> pending [label="Resume()" fontcolor="red"]; + pending -> paused [label="Pause()" fontcolor="red"]; + retry -> paused [label="Pause()" fontcolor="red"]; + running -> paused [label="Pause()" fontcolor="red"]; + + paused -> failure [label="Cancel()" fontcolor="red"]; + pending -> failure [label="Cancel()" fontcolor="red"]; + retry -> failure [label="Cancel()" fontcolor="red"]; + running -> failure [label="Cancel()" fontcolor="red"]; +}
--- a/Resources/LinuxStandardBaseToolchain.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/LinuxStandardBaseToolchain.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -1,14 +1,16 @@ +# 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 + INCLUDE(CMakeForceCompiler) SET(LSB_PATH $ENV{LSB_PATH}) +SET(LSB_CC $ENV{LSB_CC}) +SET(LSB_CXX $ENV{LSB_CXX}) SET(LSB_TARGET_VERSION "4.0") IF ("${LSB_PATH}" STREQUAL "") SET(LSB_PATH "/opt/lsb") ENDIF() -message("Using the following Linux Standard Base: ${LSB_PATH}") - IF (EXISTS ${LSB_PATH}/lib64) SET(LSB_TARGET_PROCESSOR "x86_64") SET(LSB_LIBPATH ${LSB_PATH}/lib64-${LSB_TARGET_VERSION}) @@ -38,5 +40,27 @@ # search headers and libraries in the target environment, search # programs in the host environment SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + +SET(CMAKE_CROSSCOMPILING OFF) + + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include" CACHE INTERNAL "" FORCE) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward" CACHE INTERNAL "" FORCE) +SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE) +SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE) + +if (NOT "${LSB_CXX}" STREQUAL "") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cxx=${LSB_CXX}") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}") +endif() + +if (NOT "${LSB_CC}" STREQUAL "") + SET(CMAKE_C_FLAGS "${CMAKE_CC_FLAGS} --lsb-cc=${LSB_CC}") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cc=${LSB_CC}") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cc=${LSB_CC}") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cc=${LSB_CC}") +endif() +
--- a/Resources/MinGWToolchain.cmake Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/MinGWToolchain.cmake Fri Oct 12 15:18:10 2018 +0200 @@ -15,3 +15,6 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE)
--- a/Resources/OldBuildInstructions.txt Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/OldBuildInstructions.txt Fri Oct 12 15:18:10 2018 +0200 @@ -32,7 +32,7 @@ # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ -DUSE_SYSTEM_PUGIXML=OFF \ -DENABLE_JPEG=OFF \ -DENABLE_JPEG_LOSSLESS=OFF \ @@ -55,7 +55,7 @@ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ -DUSE_SYSTEM_PUGIXML=OFF \ ~/Orthanc @@ -66,7 +66,7 @@ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ -DUSE_SYSTEM_PUGIXML=OFF \ -DENABLE_JPEG=OFF \ -DENABLE_JPEG_LOSSLESS=OFF \ @@ -85,7 +85,7 @@ # cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \ -DUSE_SYSTEM_PUGIXML=OFF \ -DENABLE_JPEG=OFF \ -DENABLE_JPEG_LOSSLESS=OFF \
--- a/Resources/Orthanc.doxygen Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Orthanc.doxygen Fri Oct 12 15:18:10 2018 +0200 @@ -1,110 +1,129 @@ -# Doxyfile 1.8.1.2 +# Doxyfile 1.8.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # -# All text after a hash (#) is considered a comment and will be ignored. +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. # The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" "). +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or sequence of words) that should -# identify the project. Note that if you do not use Doxywizard you need -# to put quotes around the project name if it contains spaces. +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. PROJECT_NAME = Orthanc -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer -# a quick idea about the purpose of the project. Keep the description short. +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Internal documentation of Orthanc" -# With the PROJECT_LOGO tag one can specify an logo or icon that is -# included in the documentation. The maximum height of the logo should not -# exceed 55 pixels and the maximum width should not exceed 200 pixels. -# Doxygen will copy the logo to the output directory. +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. PROJECT_LOGO = @CMAKE_SOURCE_DIR@/Resources/OrthancLogoDocumentation.png -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. OUTPUT_DIRECTORY = OrthancInternalDocumentation -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. CREATE_SUBDIRS = NO +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. +# The default value is: YES. REPEAT_BRIEF = YES -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief +# doxygen will generate a detailed section even if there is only a brief # description. +# The default value is: NO. ALWAYS_DETAILED_SEC = NO @@ -112,169 +131,207 @@ # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. +# The default value is: NO. INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. FULL_PATH_NAMES = NO -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful if your file system -# doesn't support long names like on DOS, Mac, or CD-ROM. +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. SEPARATE_MEMBER_PAGES = NO -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 8 -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding -# "class=itcl::class" will allow you to use the command class in the -# itcl::class meaning. +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. TCL_SUBST = -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this -# tag. The format is ext=language, where ext is a file extension, and language -# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, -# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions -# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. EXTENSION_MAPPING = -# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all -# comments according to the Markdown format, which allows for more readable +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you -# can mix doxygen, HTML, and XML commands with Markdown formatting. -# Disable only in case of backward compatibilities issues. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. MARKDOWN_SUPPORT = YES +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also makes the inheritance and collaboration +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. +# The default value is: NO. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. +# The default value is: NO. CPP_CLI_SUPPORT = NO -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. SIP_SUPPORT = NO -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. IDL_PROPERTY_SUPPORT = YES @@ -282,67 +339,61 @@ # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. +# The default value is: NO. DISTRIBUTE_GROUP_DOC = NO -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. SUBGROUPING = YES -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and -# unions are shown inside the group in which they are included (e.g. using -# @ingroup) instead of on a separate page (for HTML and Man pages) or -# section (for LaTeX and RTF). +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. INLINE_GROUPED_CLASSES = NO -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and -# unions with only public data fields will be shown inline in the documentation -# of the scope in which they are defined (i.e. file, namespace, or group -# documentation), provided this scope is documented. If set to NO (the default), -# structs, classes, and unions are shown on a separate page (for HTML and Man -# pages) or section (for LaTeX and RTF). +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. INLINE_SIMPLE_STRUCTS = NO -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. TYPEDEF_HIDES_STRUCT = NO -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penalty. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will roughly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. - -SYMBOL_CACHE_SIZE = 0 - -# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be -# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given -# their name and scope. Since this can be an expensive process and often the -# same symbol appear multiple times in the code, doxygen keeps a cache of -# pre-resolved symbols. If the cache is too small doxygen will become slower. -# If the cache is too large, memory is wasted. The cache size is given by this -# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 @@ -351,340 +402,391 @@ #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. EXTRACT_ALL = YES -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. EXTRACT_PRIVATE = NO -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. EXTRACT_PACKAGE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. EXTRACT_STATIC = NO -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. EXTRACT_LOCAL_CLASSES = YES -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespaces are hidden. +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. HIDE_IN_BODY_DOCS = NO -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. +# The default value is: system dependent. CASE_SENSE_NAMES = YES -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. SHOW_INCLUDE_FILES = YES -# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. FORCE_LOCAL_INCLUDES = NO -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen -# will sort the (brief and detailed) documentation of class members so that -# constructors and destructors are listed first. If set to NO (the default) -# the constructors will appear in the respective orders defined by -# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. -# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO -# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. SORT_GROUP_NAMES = NO -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to -# do proper type resolution of all parameters of a function it will reject a -# match between the prototype and the implementation of a member function even -# if there is only one candidate or it is obvious which candidate to choose -# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen -# will still accept a match between prototype and implementation in such cases. +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. STRICT_PROTO_MATCHING = NO -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. GENERATE_DEPRECATEDLIST= YES -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. ENABLED_SECTIONS = -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or macro consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and macros in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. SHOW_USED_FILES = YES -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. SHOW_FILES = YES -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. -# This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via -# popen()) the command <command> <input-file>, where <command> is the value of -# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. -# You can optionally specify a file name after the option, if omitted -# DoxygenLayout.xml will be used as the name of the layout file. +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. LAYOUT_FILE = -# The CITE_BIB_FILES tag can be used to specify one or more bib files -# containing the references data. This must be a list of .bib files. The -# .bib extension is automatically appended if omitted. Using this command -# requires the bibtex tool to be installed. See also -# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style -# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this -# feature you need bibtex and perl available in the search path. +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. WARNINGS = YES -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. WARN_IF_DOC_ERROR = YES -# The WARN_NO_PARAMDOC option can be enabled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. WARN_NO_PARAMDOC = NO -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. INPUT = @CMAKE_SOURCE_DIR@/Core \ @CMAKE_SOURCE_DIR@/OrthancServer # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh -# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py -# *.f90 *.f *.for *.vhd *.vhdl +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. FILE_PATTERNS = *.h -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. +# # Note that relative paths are relative to the directory from which doxygen is # run. @@ -693,14 +795,16 @@ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. +# The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = @@ -709,755 +813,1076 @@ # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = Orthanc::Internals -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. EXAMPLE_RECURSIVE = NO -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command <filter> <input-file>, where <filter> -# is the value of the INPUT_FILTER tag, and <input-file> is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. -# If FILTER_PATTERNS is specified, this tag will be -# ignored. +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. -# Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. -# The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty or if -# non of the patterns match the file name, INPUT_FILTER is applied. +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) -# and it is also possible to disable source filtering for a specific pattern -# using *.ext= (so without naming a filter). This option only has effect when -# FILTER_SOURCE_FILES is enabled. +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. SOURCE_BROWSER = NO -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C, C++ and Fortran comments will always remain visible. +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. STRIP_CODE_COMMENTS = YES -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. REFERENCED_BY_RELATION = NO -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. REFERENCES_RELATION = NO -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. -# Otherwise they will link to the documentation. +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. REFERENCES_LINK_SOURCE = YES -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. ALPHABETICAL_INDEX = YES -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. GENERATE_HTML = YES -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = doc -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. Note that when using a custom header you are responsible -# for the proper inclusion of any scripts and style sheets that doxygen -# needs, which is dependent on the configuration options used. -# It is advised to generate a default header using "doxygen -w html -# header.html footer.html stylesheet.css YourConfigFile" and then modify -# that header. Note that the header is subject to change so you typically -# have to redo this when upgrading to a newer version of doxygen or when -# changing the value of configuration settings such as GENERATE_TREEVIEW! +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# style sheet in the HTML output directory as well, or it will be erased! +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the -# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that -# the files will be copied as-is; there are no commands or markers available. +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. -# Doxygen will adjust the colors in the style sheet and background images -# according to this color. Hue is specified as an angle on a colorwheel, -# see http://en.wikipedia.org/wiki/Hue for more information. -# For instance the value 0 represents red, 60 is yellow, 120 is green, -# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. -# The allowed range is 0 to 359. +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of -# the colors in the HTML output. For a value of 0 the output will use -# grayscales only. A value of 255 will produce the most vivid colors. +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to -# the luminance component of the colors in the HTML output. Values below -# 100 gradually make the output lighter, whereas values above 100 make -# the output darker. The value divided by 100 is the actual gamma applied, -# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, -# and 100 does not change the gamma. +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of -# entries shown in the various tree structured indices initially; the user -# can expand and collapse entries dynamically later on. Doxygen will expand -# the tree to such a level that at most the specified number of entries are -# visible (unless a fully collapsed tree already exceeds this amount). -# So setting the number of entries 1 will produce a full collapsed tree by -# default. 0 is a special value representing an infinite number of entries -# and will result in a full expanded tree by default. +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project -# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher -# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be # written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated -# that can be used as input for Qt's qhelpgenerator to generate a -# Qt Compressed Help (.qch) of the generated HTML documentation. +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to -# add. For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see -# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> -# Qt Help Project / Custom Filters</a>. +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's -# filter section matches. -# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> -# Qt Help Project / Filter Attributes</a>. +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before -# the help appears. +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project -# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) -# at top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. Since the tabs have the same information as the -# navigation tree you can set this option to NO if you already set -# GENERATE_TREEVIEW to YES. +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. -# Since the tree basically has the same information as the tab index you -# could consider to set DISABLE_INDEX to NO when enabling this option. +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values -# (range [0,1..20]) that doxygen will group on one line in the generated HTML -# documentation. Note that a value of 0 will completely suppress the enum -# values from appearing in the overview section. +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 1 -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open -# links to external symbols imported via tag files in a separate window. +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are -# not supported properly for IE 6.0, but are supported on all modern browsers. -# Note that when changing this option you need to delete any form_*.png files -# in the HTML output before the changes have effect. +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax -# (see http://www.mathjax.org) which uses client side Javascript for the -# rendering instead of using prerendered bitmaps. Use this if you do not -# have LaTeX installed or if you want to formulas look prettier in the HTML -# output. When enabled you may also need to install MathJax separately and -# configure the path to it using the MATHJAX_RELPATH option. +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO -# When MathJax is enabled you need to specify the location relative to the -# HTML output directory using the MATHJAX_RELPATH option. The destination -# directory should contain the MathJax.js script. For instance, if the mathjax -# directory is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to -# the MathJax Content Delivery Network so you can quickly see the result without -# installing MathJax. -# However, it is strongly recommended to install a local -# copy of MathJax from http://www.mathjax.org before deployment. +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://www.mathjax.org/mathjax -# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension -# names that should be enabled during MathJax rendering. +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box -# for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using -# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets -# (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a PHP enabled web server instead of at the web client -# using Javascript. Doxygen will generate the search PHP script and index -# file to put on the web server. The advantage of the server -# based approach is that it scales better to large projects and allows -# full text search. The disadvantages are that it is more difficult to setup -# and does not have live searching capabilities. +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. SERVER_BASED_SEARCH = NO +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + #--------------------------------------------------------------------------- -# configuration options related to the LaTeX output +# Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. +# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. +# The default value is: YES. GENERATE_LATEX = NO -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. -# Note that when enabling USE_PDFLATEX this option is only used for -# generating bitmaps for formulas in the HTML output, but not in the -# Makefile that is written to the output directory. +# invoked. +# +# Note that when enabling USE_PDFLATEX this option is only used for generating +# bitmaps for formulas in the HTML output, but not in the Makefile that is +# written to the output directory. +# The default file is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. +# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. COMPACT_LATEX = NO -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, letter, legal and -# executive. If left blank a4wide will be used. +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. PAPER_TYPE = a4 -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. To get the times font for +# instance you can specify +# EXTRA_PACKAGES=times +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will +# replace them by respectively the title of the page, the current date and time, +# only the current date, the version number of doxygen, the project name (see +# PROJECT_NAME), or the project number (see PROJECT_NUMBER). +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for -# the generated latex document. The footer should contain everything after -# the last chapter. If it is left blank doxygen will generate a -# standard footer. Notice: only use this tag if you know what you are doing! +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a +# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES to get a # higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HIDE_INDICES = NO -# If LATEX_SOURCE_CODE is set to YES then doxygen will include -# source code with syntax highlighting in the LaTeX output. -# Note that which sources are shown also depends on other settings -# such as SOURCE_BROWSER. +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See -# http://en.wikipedia.org/wiki/BibTeX for more info. +# bibliography, e.g. plainnat, or ieeetr. See +# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- -# configuration options related to the RTF output +# Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. +# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. GENERATE_RTF = NO -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. +# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. COMPACT_RTF = NO -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_HYPERLINKS = NO -# Load style sheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's config +# file, i.e. a series of assignments. You only have to provide replacements, +# missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's config file. A template extensions file can be generated +# using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- -# configuration options related to the man page output +# Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages +# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for +# classes and files. +# The default value is: NO. GENERATE_MAN = NO -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_OUTPUT = man -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_EXTENSION = .3 -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_LINKS = NO #--------------------------------------------------------------------------- -# configuration options related to the XML output +# Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. +# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. GENERATE_XML = NO -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. +# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output +# Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. +# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen +# Definitions (see http://autogen.sf.net) file that captures the structure of +# the code including all documentation. Note that this feature is still +# experimental and incomplete at the moment. +# The default value is: NO. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- -# configuration options related to the Perl module output +# Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. +# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. +# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. -# This is useful -# if you want to understand what is going on. -# On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_PRETTY = YES -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_MAKEVAR_PREFIX = @@ -1465,106 +1890,128 @@ # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. +# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. +# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names +# in the source code. If set to NO only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. MACRO_EXPANSION = NO -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. EXPAND_ONLY_PREDEF = NO -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# pointed to by INCLUDE_PATH will be searched when a #include is found. +# If the SEARCH_INCLUDES tag is set to YES the includes files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. INCLUDE_FILE_PATTERNS = -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. PREDEFINED = -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition that -# overrules the definition found in the source code. +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all references to function-like macros -# that are alone on a line, have an all uppercase name, and do not end with a -# semicolon, because these will confuse the parser if not removed. +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- -# Configuration::additions related to external references +# Configuration options related to external references #--------------------------------------------------------------------------- -# The TAGFILES option can be used to specify one or more tagfiles. For each -# tag file the location of the external documentation should be added. The -# format of a tag file without this location is as follows: -# +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: -# # TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths -# or URLs. Note that each tag file must have a unique name (where the name does -# NOT include the path). If a tag file is not located in the directory in which -# doxygen is run, you must also specify the path to the tagfile here. +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. +# If the ALLEXTERNALS tag is set to YES all external class will be listed in the +# class index. If set to NO only the inherited external classes will be listed. +# The default value is: NO. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in +# the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. EXTERNAL_GROUPS = YES +# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + # The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. PERL_PATH = /usr/bin/perl @@ -1572,222 +2019,295 @@ # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option also works with HAVE_DOT disabled, but it is recommended to -# install and use dot, since it yields more powerful graphs. +# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: YES. HAVE_DOT = NO -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is -# allowed to run in parallel. When set to 0 (the default) doxygen will -# base this on the number of processors available in the system. You can set it -# explicitly to a value larger than 0 to get control over the balance -# between CPU load and processing speed. +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_NUM_THREADS = 0 -# By default doxygen will use the Helvetica font for all dot files that -# doxygen generates. When you want a differently looking font you can specify -# the font name using DOT_FONTNAME. You need to make sure dot is able to find -# the font, which can be done by putting it in a standard location or by setting -# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the -# directory containing the font. +# When you want a differently looking font n the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTNAME = Helvetica -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTSIZE = 10 -# By default doxygen will tell dot to use the Helvetica font. -# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to -# set the path where dot can find it. +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. UML_LOOK = NO -# If the UML_LOOK tag is enabled, the fields and methods are shown inside -# the class node. If there are many fields or methods and many nodes the -# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS -# threshold limits the number of items for each type to make the size more -# managable. Set this to 0 for no limit. Note that the threshold may be -# exceeded by 50% before the limit is enforced. +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. UML_LIMIT_NUM_FIELDS = 10 -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. TEMPLATE_RELATIONS = NO -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. CALL_GRAPH = NO -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will generate a graphical hierarchy of all classes instead of a textual one. +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are svg, png, jpg, or gif. -# If left blank png will be used. If you choose svg you need to set -# HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible in IE 9+ (other browsers do not have this requirement). +# generated by dot. +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, +# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, +# gif:cairo:gd, gif:gd, gif:gd:gd and svg. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. -# Note that this requires a modern browser other than Internet Explorer. -# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you -# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible. Older versions of IE do not have SVG support. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. INTERACTIVE_SVG = NO -# The tag DOT_PATH can be used to specify the path where the dot tool can be +# The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the -# \mscfile command). +# contain msc files that are included in the documentation (see the \mscfile +# command). MSCFILE_DIRS = -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_GRAPH_MAX_NODES = 50 -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_MULTI_TARGETS = YES -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. +# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES
--- a/Resources/OrthancPlugin.doxygen Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/OrthancPlugin.doxygen Fri Oct 12 15:18:10 2018 +0200 @@ -1,110 +1,129 @@ -# Doxyfile 1.8.1.2 +# Doxyfile 1.8.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # -# All text after a hash (#) is considered a comment and will be ignored. +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. # The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" "). +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or sequence of words) that should -# identify the project. Note that if you do not use Doxywizard you need -# to put quotes around the project name if it contains spaces. +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. PROJECT_NAME = "Orthanc Plugin SDK" -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @ORTHANC_VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer -# a quick idea about the purpose of the project. Keep the description short. +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Documentation of the plugin interface of Orthanc" -# With the PROJECT_LOGO tag one can specify an logo or icon that is -# included in the documentation. The maximum height of the logo should not -# exceed 55 pixels and the maximum width should not exceed 200 pixels. -# Doxygen will copy the logo to the output directory. +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. PROJECT_LOGO = @CMAKE_SOURCE_DIR@/Resources/OrthancLogoDocumentation.png -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. OUTPUT_DIRECTORY = OrthancPluginDocumentation -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. CREATE_SUBDIRS = NO +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. +# The default value is: YES. REPEAT_BRIEF = NO -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief +# doxygen will generate a detailed section even if there is only a brief # description. +# The default value is: NO. ALWAYS_DETAILED_SEC = NO @@ -112,169 +131,207 @@ # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. +# The default value is: NO. INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. FULL_PATH_NAMES = NO -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful if your file system -# doesn't support long names like on DOS, Mac, or CD-ROM. +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. SEPARATE_MEMBER_PAGES = NO -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 8 -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding -# "class=itcl::class" will allow you to use the command class in the -# itcl::class meaning. +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. TCL_SUBST = -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this -# tag. The format is ext=language, where ext is a file extension, and language -# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, -# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions -# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. EXTENSION_MAPPING = -# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all -# comments according to the Markdown format, which allows for more readable +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you -# can mix doxygen, HTML, and XML commands with Markdown formatting. -# Disable only in case of backward compatibilities issues. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. MARKDOWN_SUPPORT = YES +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also makes the inheritance and collaboration +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. +# The default value is: NO. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. +# The default value is: NO. CPP_CLI_SUPPORT = NO -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. SIP_SUPPORT = NO -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. IDL_PROPERTY_SUPPORT = YES @@ -282,67 +339,61 @@ # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. +# The default value is: NO. DISTRIBUTE_GROUP_DOC = NO -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. SUBGROUPING = YES -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and -# unions are shown inside the group in which they are included (e.g. using -# @ingroup) instead of on a separate page (for HTML and Man pages) or -# section (for LaTeX and RTF). +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. INLINE_GROUPED_CLASSES = NO -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and -# unions with only public data fields will be shown inline in the documentation -# of the scope in which they are defined (i.e. file, namespace, or group -# documentation), provided this scope is documented. If set to NO (the default), -# structs, classes, and unions are shown on a separate page (for HTML and Man -# pages) or section (for LaTeX and RTF). +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. INLINE_SIMPLE_STRUCTS = NO -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. TYPEDEF_HIDES_STRUCT = NO -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penalty. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will roughly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. - -SYMBOL_CACHE_SIZE = 0 - -# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be -# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given -# their name and scope. Since this can be an expensive process and often the -# same symbol appear multiple times in the code, doxygen keeps a cache of -# pre-resolved symbols. If the cache is too small doxygen will become slower. -# If the cache is too large, memory is wasted. The cache size is given by this -# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 @@ -351,341 +402,391 @@ #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. EXTRACT_ALL = NO -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. EXTRACT_PRIVATE = NO -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. EXTRACT_PACKAGE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. EXTRACT_STATIC = NO -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. EXTRACT_LOCAL_CLASSES = YES -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespaces are hidden. +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. HIDE_FRIEND_COMPOUNDS = YES -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. HIDE_IN_BODY_DOCS = NO -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. +# The default value is: system dependent. CASE_SENSE_NAMES = YES -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. SHOW_INCLUDE_FILES = YES -# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. FORCE_LOCAL_INCLUDES = NO -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen -# will sort the (brief and detailed) documentation of class members so that -# constructors and destructors are listed first. If set to NO (the default) -# the constructors will appear in the respective orders defined by -# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. -# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO -# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. SORT_GROUP_NAMES = NO -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to -# do proper type resolution of all parameters of a function it will reject a -# match between the prototype and the implementation of a member function even -# if there is only one candidate or it is obvious which candidate to choose -# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen -# will still accept a match between prototype and implementation in such cases. +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. STRICT_PROTO_MATCHING = NO -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. GENERATE_DEPRECATEDLIST= YES -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. ENABLED_SECTIONS = -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or macro consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and macros in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 0 -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. SHOW_USED_FILES = YES -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. SHOW_FILES = YES -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. -# This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via -# popen()) the command <command> <input-file>, where <command> is the value of -# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. -# You can optionally specify a file name after the option, if omitted -# DoxygenLayout.xml will be used as the name of the layout file. +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. LAYOUT_FILE = -# The CITE_BIB_FILES tag can be used to specify one or more bib files -# containing the references data. This must be a list of .bib files. The -# .bib extension is automatically appended if omitted. Using this command -# requires the bibtex tool to be installed. See also -# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style -# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this -# feature you need bibtex and perl available in the search path. +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. WARNINGS = YES -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. WARN_IF_DOC_ERROR = YES -# The WARN_NO_PARAMDOC option can be enabled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. WARN_NO_PARAMDOC = YES -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. INPUT = @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCPlugin.h \ - @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCDatabasePlugin.h \ - @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h + @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCDatabasePlugin.h # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh -# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py -# *.f90 *.f *.for *.vhd *.vhdl +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. FILE_PATTERNS = *.h -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. +# # Note that relative paths are relative to the directory from which doxygen is # run. @@ -694,14 +795,16 @@ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. +# The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = @@ -710,755 +813,1077 @@ # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test - -EXCLUDE_SYMBOLS = _OrthancPlugin* OrthancPluginGetName - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = _OrthancPlugin* \ + OrthancPluginGetName + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. EXAMPLE_RECURSIVE = NO -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command <filter> <input-file>, where <filter> -# is the value of the INPUT_FILTER tag, and <input-file> is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. -# If FILTER_PATTERNS is specified, this tag will be -# ignored. +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. -# Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. -# The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty or if -# non of the patterns match the file name, INPUT_FILTER is applied. +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) -# and it is also possible to disable source filtering for a specific pattern -# using *.ext= (so without naming a filter). This option only has effect when -# FILTER_SOURCE_FILES is enabled. +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. SOURCE_BROWSER = NO -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C, C++ and Fortran comments will always remain visible. +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. STRIP_CODE_COMMENTS = YES -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. REFERENCED_BY_RELATION = NO -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. REFERENCES_RELATION = NO -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. -# Otherwise they will link to the documentation. +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. REFERENCES_LINK_SOURCE = YES -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. ALPHABETICAL_INDEX = YES -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. GENERATE_HTML = YES -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = doc -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. Note that when using a custom header you are responsible -# for the proper inclusion of any scripts and style sheets that doxygen -# needs, which is dependent on the configuration options used. -# It is advised to generate a default header using "doxygen -w html -# header.html footer.html stylesheet.css YourConfigFile" and then modify -# that header. Note that the header is subject to change so you typically -# have to redo this when upgrading to a newer version of doxygen or when -# changing the value of configuration settings such as GENERATE_TREEVIEW! +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# style sheet in the HTML output directory as well, or it will be erased! +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the -# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that -# the files will be copied as-is; there are no commands or markers available. +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. -# Doxygen will adjust the colors in the style sheet and background images -# according to this color. Hue is specified as an angle on a colorwheel, -# see http://en.wikipedia.org/wiki/Hue for more information. -# For instance the value 0 represents red, 60 is yellow, 120 is green, -# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. -# The allowed range is 0 to 359. +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of -# the colors in the HTML output. For a value of 0 the output will use -# grayscales only. A value of 255 will produce the most vivid colors. +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to -# the luminance component of the colors in the HTML output. Values below -# 100 gradually make the output lighter, whereas values above 100 make -# the output darker. The value divided by 100 is the actual gamma applied, -# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, -# and 100 does not change the gamma. +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of -# entries shown in the various tree structured indices initially; the user -# can expand and collapse entries dynamically later on. Doxygen will expand -# the tree to such a level that at most the specified number of entries are -# visible (unless a fully collapsed tree already exceeds this amount). -# So setting the number of entries 1 will produce a full collapsed tree by -# default. 0 is a special value representing an infinite number of entries -# and will result in a full expanded tree by default. +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project -# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher -# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be # written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated -# that can be used as input for Qt's qhelpgenerator to generate a -# Qt Compressed Help (.qch) of the generated HTML documentation. +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to -# add. For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see -# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> -# Qt Help Project / Custom Filters</a>. +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's -# filter section matches. -# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> -# Qt Help Project / Filter Attributes</a>. +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before -# the help appears. +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project -# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) -# at top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. Since the tabs have the same information as the -# navigation tree you can set this option to NO if you already set -# GENERATE_TREEVIEW to YES. +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. -# Since the tree basically has the same information as the tab index you -# could consider to set DISABLE_INDEX to NO when enabling this option. +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values -# (range [0,1..20]) that doxygen will group on one line in the generated HTML -# documentation. Note that a value of 0 will completely suppress the enum -# values from appearing in the overview section. +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 1 -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open -# links to external symbols imported via tag files in a separate window. +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are -# not supported properly for IE 6.0, but are supported on all modern browsers. -# Note that when changing this option you need to delete any form_*.png files -# in the HTML output before the changes have effect. +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax -# (see http://www.mathjax.org) which uses client side Javascript for the -# rendering instead of using prerendered bitmaps. Use this if you do not -# have LaTeX installed or if you want to formulas look prettier in the HTML -# output. When enabled you may also need to install MathJax separately and -# configure the path to it using the MATHJAX_RELPATH option. +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO -# When MathJax is enabled you need to specify the location relative to the -# HTML output directory using the MATHJAX_RELPATH option. The destination -# directory should contain the MathJax.js script. For instance, if the mathjax -# directory is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to -# the MathJax Content Delivery Network so you can quickly see the result without -# installing MathJax. -# However, it is strongly recommended to install a local -# copy of MathJax from http://www.mathjax.org before deployment. +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://www.mathjax.org/mathjax -# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension -# names that should be enabled during MathJax rendering. +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box -# for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using -# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets -# (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a PHP enabled web server instead of at the web client -# using Javascript. Doxygen will generate the search PHP script and index -# file to put on the web server. The advantage of the server -# based approach is that it scales better to large projects and allows -# full text search. The disadvantages are that it is more difficult to setup -# and does not have live searching capabilities. +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. SERVER_BASED_SEARCH = NO +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + #--------------------------------------------------------------------------- -# configuration options related to the LaTeX output +# Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. +# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. +# The default value is: YES. GENERATE_LATEX = NO -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. -# Note that when enabling USE_PDFLATEX this option is only used for -# generating bitmaps for formulas in the HTML output, but not in the -# Makefile that is written to the output directory. +# invoked. +# +# Note that when enabling USE_PDFLATEX this option is only used for generating +# bitmaps for formulas in the HTML output, but not in the Makefile that is +# written to the output directory. +# The default file is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. +# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. COMPACT_LATEX = NO -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, letter, legal and -# executive. If left blank a4wide will be used. +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. PAPER_TYPE = a4 -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. To get the times font for +# instance you can specify +# EXTRA_PACKAGES=times +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will +# replace them by respectively the title of the page, the current date and time, +# only the current date, the version number of doxygen, the project name (see +# PROJECT_NAME), or the project number (see PROJECT_NUMBER). +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for -# the generated latex document. The footer should contain everything after -# the last chapter. If it is left blank doxygen will generate a -# standard footer. Notice: only use this tag if you know what you are doing! +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a +# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES to get a # higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HIDE_INDICES = NO -# If LATEX_SOURCE_CODE is set to YES then doxygen will include -# source code with syntax highlighting in the LaTeX output. -# Note that which sources are shown also depends on other settings -# such as SOURCE_BROWSER. +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See -# http://en.wikipedia.org/wiki/BibTeX for more info. +# bibliography, e.g. plainnat, or ieeetr. See +# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- -# configuration options related to the RTF output +# Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. +# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. GENERATE_RTF = NO -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. +# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. COMPACT_RTF = NO -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_HYPERLINKS = NO -# Load style sheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's config +# file, i.e. a series of assignments. You only have to provide replacements, +# missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's config file. A template extensions file can be generated +# using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- -# configuration options related to the man page output +# Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages +# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for +# classes and files. +# The default value is: NO. GENERATE_MAN = NO -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_OUTPUT = man -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_EXTENSION = .3 -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. MAN_LINKS = NO #--------------------------------------------------------------------------- -# configuration options related to the XML output +# Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. +# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. GENERATE_XML = NO -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. +# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output +# Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. +# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen +# Definitions (see http://autogen.sf.net) file that captures the structure of +# the code including all documentation. Note that this feature is still +# experimental and incomplete at the moment. +# The default value is: NO. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- -# configuration options related to the Perl module output +# Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. +# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. +# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. -# This is useful -# if you want to understand what is going on. -# On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_PRETTY = YES -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. PERLMOD_MAKEVAR_PREFIX = @@ -1466,106 +1891,128 @@ # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. +# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. +# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names +# in the source code. If set to NO only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. MACRO_EXPANSION = YES -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. EXPAND_ONLY_PREDEF = YES -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# pointed to by INCLUDE_PATH will be searched when a #include is found. +# If the SEARCH_INCLUDES tag is set to YES the includes files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. INCLUDE_FILE_PATTERNS = -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. PREDEFINED = ORTHANC_PLUGIN_INLINE= -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition that -# overrules the definition found in the source code. +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all references to function-like macros -# that are alone on a line, have an all uppercase name, and do not end with a -# semicolon, because these will confuse the parser if not removed. +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- -# Configuration::additions related to external references +# Configuration options related to external references #--------------------------------------------------------------------------- -# The TAGFILES option can be used to specify one or more tagfiles. For each -# tag file the location of the external documentation should be added. The -# format of a tag file without this location is as follows: -# +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: -# # TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths -# or URLs. Note that each tag file must have a unique name (where the name does -# NOT include the path). If a tag file is not located in the directory in which -# doxygen is run, you must also specify the path to the tagfile here. +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. +# If the ALLEXTERNALS tag is set to YES all external class will be listed in the +# class index. If set to NO only the inherited external classes will be listed. +# The default value is: NO. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in +# the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. EXTERNAL_GROUPS = YES +# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + # The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. PERL_PATH = /usr/bin/perl @@ -1573,222 +2020,295 @@ # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option also works with HAVE_DOT disabled, but it is recommended to -# install and use dot, since it yields more powerful graphs. +# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: YES. HAVE_DOT = NO -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is -# allowed to run in parallel. When set to 0 (the default) doxygen will -# base this on the number of processors available in the system. You can set it -# explicitly to a value larger than 0 to get control over the balance -# between CPU load and processing speed. +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_NUM_THREADS = 0 -# By default doxygen will use the Helvetica font for all dot files that -# doxygen generates. When you want a differently looking font you can specify -# the font name using DOT_FONTNAME. You need to make sure dot is able to find -# the font, which can be done by putting it in a standard location or by setting -# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the -# directory containing the font. +# When you want a differently looking font n the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTNAME = Helvetica -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTSIZE = 10 -# By default doxygen will tell dot to use the Helvetica font. -# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to -# set the path where dot can find it. +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. UML_LOOK = NO -# If the UML_LOOK tag is enabled, the fields and methods are shown inside -# the class node. If there are many fields or methods and many nodes the -# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS -# threshold limits the number of items for each type to make the size more -# managable. Set this to 0 for no limit. Note that the threshold may be -# exceeded by 50% before the limit is enforced. +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. UML_LIMIT_NUM_FIELDS = 10 -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. TEMPLATE_RELATIONS = NO -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. CALL_GRAPH = NO -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will generate a graphical hierarchy of all classes instead of a textual one. +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are svg, png, jpg, or gif. -# If left blank png will be used. If you choose svg you need to set -# HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible in IE 9+ (other browsers do not have this requirement). +# generated by dot. +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, +# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, +# gif:cairo:gd, gif:gd, gif:gd:gd and svg. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. -# Note that this requires a modern browser other than Internet Explorer. -# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you -# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible. Older versions of IE do not have SVG support. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. INTERACTIVE_SVG = NO -# The tag DOT_PATH can be used to specify the path where the dot tool can be +# The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the -# \mscfile command). +# contain msc files that are included in the documentation (see the \mscfile +# command). MSCFILE_DIRS = -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_GRAPH_MAX_NODES = 50 -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_MULTI_TARGETS = YES -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. +# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/boost-1.65.1-linux-standard-base.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,12 @@ +diff -urEb boost_1_65_1.orig/boost/move/adl_move_swap.hpp boost_1_65_1/boost/move/adl_move_swap.hpp +--- boost_1_65_1.orig/boost/move/adl_move_swap.hpp 2017-11-08 17:43:20.000000000 +0100 ++++ boost_1_65_1/boost/move/adl_move_swap.hpp 2018-01-02 15:34:48.829052917 +0100 +@@ -28,6 +28,8 @@ + //Try to avoid including <algorithm>, as it's quite big + #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB) + #include <utility> //Dinkum libraries define std::swap in utility which is lighter than algorithm ++#elif defined(__LSB_VERSION__) ++# include <utility> + #elif defined(BOOST_GNU_STDLIB) + //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions + //use the good old stl_algobase header, which is quite lightweight
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/boost-1.66.0-linux-standard-base.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,13 @@ +diff -urEb boost_1_66_0.orig/boost/move/adl_move_swap.hpp boost_1_66_0/boost/move/adl_move_swap.hpp +--- boost_1_66_0.orig/boost/move/adl_move_swap.hpp 2018-04-11 11:56:16.761768726 +0200 ++++ boost_1_66_0/boost/move/adl_move_swap.hpp 2018-04-11 11:57:01.073881330 +0200 +@@ -28,6 +28,8 @@ + //Try to avoid including <algorithm>, as it's quite big + #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB) + #include <utility> //Dinkum libraries define std::swap in utility which is lighter than algorithm ++#elif defined(__LSB_VERSION__) ++# include <utility> + #elif defined(BOOST_GNU_STDLIB) + //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions + //use the good old stl_algobase header, which is quite lightweight +Only in boost_1_66_0/boost/move: adl_move_swap.hpp~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/boost-1.67.0-linux-standard-base.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,74 @@ +diff -urEb boost_1_67_0.orig/boost/move/adl_move_swap.hpp boost_1_67_0/boost/move/adl_move_swap.hpp +--- boost_1_67_0.orig/boost/move/adl_move_swap.hpp 2018-06-20 17:42:27.000000000 +0200 ++++ boost_1_67_0/boost/move/adl_move_swap.hpp 2018-10-12 14:27:41.368076902 +0200 +@@ -28,6 +28,8 @@ + //Try to avoid including <algorithm>, as it's quite big + #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB) + #include <utility> //Dinkum libraries define std::swap in utility which is lighter than algorithm ++#elif defined(__LSB_VERSION__) ++# include <utility> + #elif defined(BOOST_GNU_STDLIB) + //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions + //use the good old stl_algobase header, which is quite lightweight +diff -urEb boost_1_67_0.orig/boost/thread/detail/config.hpp boost_1_67_0/boost/thread/detail/config.hpp +--- boost_1_67_0.orig/boost/thread/detail/config.hpp 2018-06-20 17:42:27.000000000 +0200 ++++ boost_1_67_0/boost/thread/detail/config.hpp 2018-10-12 14:27:41.372076898 +0200 +@@ -417,6 +417,8 @@ + #define BOOST_THREAD_INTERNAL_CLOCK_IS_MONO + #elif defined(BOOST_THREAD_CHRONO_MAC_API) + #define BOOST_THREAD_HAS_MONO_CLOCK ++#elif defined(__LSB_VERSION__) || defined(__ANDROID__) ++ #define BOOST_THREAD_HAS_MONO_CLOCK + #else + #include <time.h> // check for CLOCK_MONOTONIC + #if defined(CLOCK_MONOTONIC) +diff -urEb boost_1_67_0.orig/boost/type_traits/detail/has_postfix_operator.hpp boost_1_67_0/boost/type_traits/detail/has_postfix_operator.hpp +--- boost_1_67_0.orig/boost/type_traits/detail/has_postfix_operator.hpp 2018-06-20 17:42:27.000000000 +0200 ++++ boost_1_67_0/boost/type_traits/detail/has_postfix_operator.hpp 2018-10-12 14:31:27.539874170 +0200 +@@ -32,8 +32,11 @@ + namespace boost { + namespace detail { + ++// https://stackoverflow.com/a/15474269 ++#ifndef Q_MOC_RUN + // This namespace ensures that argument-dependent name lookup does not mess things up. + namespace BOOST_JOIN(BOOST_TT_TRAIT_NAME,_impl) { ++#endif + + // 1. a function to have an instance of type T without requiring T to be default + // constructible +@@ -181,7 +184,9 @@ + BOOST_STATIC_CONSTANT(bool, value = (trait_impl1 < Lhs_noref, Ret, BOOST_TT_FORBIDDEN_IF >::value)); + }; + ++#ifndef Q_MOC_RUN + } // namespace impl ++#endif + } // namespace detail + + // this is the accessible definition of the trait to end user +diff -urEb boost_1_67_0.orig/boost/type_traits/detail/has_prefix_operator.hpp boost_1_67_0/boost/type_traits/detail/has_prefix_operator.hpp +--- boost_1_67_0.orig/boost/type_traits/detail/has_prefix_operator.hpp 2018-06-20 17:42:27.000000000 +0200 ++++ boost_1_67_0/boost/type_traits/detail/has_prefix_operator.hpp 2018-10-12 14:31:40.991862281 +0200 +@@ -45,8 +45,11 @@ + namespace boost { + namespace detail { + ++// https://stackoverflow.com/a/15474269 ++#ifndef Q_MOC_RUN + // This namespace ensures that argument-dependent name lookup does not mess things up. + namespace BOOST_JOIN(BOOST_TT_TRAIT_NAME,_impl) { ++#endif + + // 1. a function to have an instance of type T without requiring T to be default + // constructible +@@ -194,7 +197,9 @@ + BOOST_STATIC_CONSTANT(bool, value = (trait_impl1 < Rhs_noref, Ret, BOOST_TT_FORBIDDEN_IF >::value)); + }; + ++#ifndef Q_MOC_RUN + } // namespace impl ++#endif + } // namespace detail + + // this is the accessible definition of the trait to end user
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/curl-7.57.0-cmake.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,12 @@ +diff -urEb curl-7.57.0.orig/CMake/Macros.cmake curl-7.57.0/CMake/Macros.cmake +--- curl-7.57.0.orig/CMake/Macros.cmake 2017-11-09 23:40:36.000000000 +0100 ++++ curl-7.57.0/CMake/Macros.cmake 2018-01-03 10:39:15.589520034 +0100 +@@ -38,7 +38,7 @@ + message(STATUS "Performing Curl Test ${CURL_TEST}") + try_compile(${CURL_TEST} + ${CMAKE_BINARY_DIR} +- ${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c ++ ${CURL_SOURCES_DIR}/CMake/CurlTests.c + CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS} + "${CURL_TEST_ADD_LIBRARIES}" + OUTPUT_VARIABLE OUTPUT)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-3.6.0-dulparse-vulnerability.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,29 @@ +diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dulparse.cc dcmtk-3.6.0/dcmnet/libsrc/dulparse.cc +--- dcmtk-3.6.0.orig/dcmnet/libsrc/dulparse.cc 2010-12-01 09:26:36.000000000 +0100 ++++ dcmtk-3.6.0/dcmnet/libsrc/dulparse.cc 2016-12-02 15:58:49.930540033 +0100 +@@ -393,6 +393,8 @@ + return cond; + + buf += length; ++ if (presentationLength < length) ++ return EC_MemoryExhausted; + presentationLength -= length; + DCMNET_TRACE("Successfully parsed Abstract Syntax"); + break; +@@ -404,12 +406,16 @@ + cond = LST_Enqueue(&context->transferSyntaxList, (LST_NODE*)subItem); + if (cond.bad()) return cond; + buf += length; ++ if (presentationLength < length) ++ return EC_MemoryExhausted; + presentationLength -= length; + DCMNET_TRACE("Successfully parsed Transfer Syntax"); + break; + default: + cond = parseDummy(buf, &length, presentationLength); + buf += length; ++ if (presentationLength < length) ++ return EC_MemoryExhausted; + presentationLength -= length; + break; + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-3.6.0-mingw64.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,21 @@ +diff -urEb dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h +--- dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h 2010-12-17 11:50:30.000000000 +0100 ++++ dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h 2013-07-19 15:56:25.688996134 +0200 +@@ -196,7 +196,7 @@ + OFBool popen(const char *command, const char *modes) + { + if (file_) fclose(); +-#ifdef _WIN32 ++#if defined(_WIN32) && !defined(__MINGW64_VERSION_MAJOR) + file_ = _popen(command, modes); + #else + file_ = :: popen(command, modes); +@@ -258,7 +258,7 @@ + { + if (popened_) + { +-#ifdef _WIN32 ++#if defined(_WIN32) && !defined(__MINGW64_VERSION_MAJOR) + result = _pclose(file_); + #else + result = :: pclose(file_);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-3.6.0-speed.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,54 @@ +diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc dcmtk-3.6.0/dcmnet/libsrc/dul.cc +--- dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc 2017-03-17 15:49:23.043061969 +0100 ++++ dcmtk-3.6.0/dcmnet/libsrc/dul.cc 2017-03-17 15:50:44.075359547 +0100 +@@ -630,7 +630,10 @@ + if (cond.bad()) + return cond; + +- cond = PRV_NextPDUType(association, block, timeout, &pduType); ++ /* This is the first time we read from this new connection, so in case it ++ * doesn't speak DICOM, we shouldn't wait forever (= DUL_NOBLOCK). ++ */ ++ cond = PRV_NextPDUType(association, DUL_NOBLOCK, PRV_DEFAULTTIMEOUT, &pduType); + + if (cond == DUL_NETWORKCLOSED) + event = TRANS_CONN_CLOSED; +@@ -1770,7 +1773,7 @@ + // send number of socket handle in child process over anonymous pipe + DWORD bytesWritten; + char buf[20]; +- sprintf(buf, "%i", OFreinterpret_cast(int, childSocketHandle)); ++ sprintf(buf, "%i", OFstatic_cast(int, OFreinterpret_cast(size_t, childSocketHandle))); + if (!WriteFile(hChildStdInWriteDup, buf, strlen(buf) + 1, &bytesWritten, NULL)) + { + CloseHandle(hChildStdInWriteDup); +@@ -1780,7 +1783,7 @@ + // return OF_ok status code DULC_FORKEDCHILD with descriptive text + OFOStringStream stream; + stream << "New child process started with pid " << OFstatic_cast(int, pi.dwProcessId) +- << ", socketHandle " << OFreinterpret_cast(int, childSocketHandle) << OFStringStream_ends; ++ << ", socketHandle " << OFstatic_cast(int, OFreinterpret_cast(size_t, childSocketHandle)) << OFStringStream_ends; + OFSTRINGSTREAM_GETOFSTRING(stream, msg) + return makeDcmnetCondition(DULC_FORKEDCHILD, OF_ok, msg.c_str()); + } +@@ -1840,7 +1843,7 @@ + } + #endif + #endif +- setTCPBufferLength(sock); ++ //setTCPBufferLength(sock); + + #ifndef DONT_DISABLE_NAGLE_ALGORITHM + /* +diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc +--- dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc 2017-03-17 15:49:23.043061969 +0100 ++++ dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc 2017-03-17 15:49:48.467144792 +0100 +@@ -2417,7 +2417,7 @@ + return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str()); + } + #endif +- setTCPBufferLength(s); ++ //setTCPBufferLength(s); + + #ifndef DONT_DISABLE_NAGLE_ALGORITHM + /*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-3.6.2-cmath.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,22 @@ +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-linux-standard-base.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,12 @@ +diff -urEb dcmtk-3.6.2.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.2/ofstd/include/dcmtk/ofstd/offile.h +--- dcmtk-3.6.2.orig/ofstd/include/dcmtk/ofstd/offile.h 2017-07-14 17:41:11.000000000 +0200 ++++ dcmtk-3.6.2/ofstd/include/dcmtk/ofstd/offile.h 2018-01-02 13:56:04.075293459 +0100 +@@ -551,7 +551,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-3.6.2-private.dic Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,3040 @@ +# +# Copyright (C) 1994-2013, 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, Marco Eichelberg, Joerg Riesmeier +# +# Purpose: +# This is the private tag DICOM data dictionary for the dcmtk class library. +# +# +# Dictionary of Private Tags +# +# This dictionary contains the private tags defined in the following +# reference documents (in alphabetical order): +# - AGFA IMPAX 6.5.x Solution conformance statement +# - Circle Cardiovascular Imaging cmr42 3.0 conformance statement +# - David Clunie's dicom3tools package, 2002-04-20 snapshot +# - Fuji CR console, 3rd release +# - Intelerad Medical Systems Inc., Image Server +# - OCULUS Pentacam 1.17 conformance statement +# - Philips Digital Diagnost 1.3 conformance statement +# - Philips Integris H, catheterization laboratory, RIS-interface +# - Philips Intera Achieva conformance statement +# - Philips MR Achieva conformance statement +# - Siemens Somatom syngo VA40B conformance statement +# - Siemens AXIOM Artis VB30 conformance statement +# - SonoWand Invite 2.1.1 conformance statement +# - Swissvision TR4000 conformance statement +# - private tags for DCMTK anonymizer tool +# +# Each line represents an entry in the data dictionary. Each line +# has 5 fields (Tag, VR, Name, VM, Version). Entries need not be +# in ascending tag order. +# +# Entries may override existing entries. +# +# Each field must be separated by a single tab. +# The tag value may take one of two forms: +# (gggg,"CREATOR",ee) +# (gggg,"CREATOR",eeee) [eeee >= 1000] +# The first form describes a private tag that may be used with different +# element numbers as reserved by the private creator element. +# The second form describes a private tag that may only occur with a +# certain fixed element number. +# In both cases, the tag values must be in hexadecimal. +# Repeating groups are represented by indicating the range +# (gggg-o-gggg,"CREATOR",ee) or (gggg-o-gggg,"CREATOR",eeee) +# where "-o-" indicates that only odd group numbers match the definition. +# The element part of the tag can also be a range. +# +# Comments have a '#' at the beginning of the line. +# +# Tag VR Name VM Version / Description +# +(0019,"1.2.840.113681",10) ST CRImageParamsCommon 1 PrivateTag +(0019,"1.2.840.113681",11) ST CRImageIPParamsSingle 1 PrivateTag +(0019,"1.2.840.113681",12) ST CRImageIPParamsLeft 1 PrivateTag +(0019,"1.2.840.113681",13) ST CRImageIPParamsRight 1 PrivateTag + +(0087,"1.2.840.113708.794.1.1.2.0",10) CS MediaType 1 PrivateTag +(0087,"1.2.840.113708.794.1.1.2.0",20) CS MediaLocation 1 PrivateTag +(0087,"1.2.840.113708.794.1.1.2.0",50) IS EstimatedRetrieveTime 1 PrivateTag + +(0009,"ACUSON",00) IS Unknown 1 PrivateTag +(0009,"ACUSON",01) IS Unknown 1 PrivateTag +(0009,"ACUSON",02) UN Unknown 1 PrivateTag +(0009,"ACUSON",03) UN Unknown 1 PrivateTag +(0009,"ACUSON",04) UN Unknown 1 PrivateTag +(0009,"ACUSON",05) UN Unknown 1 PrivateTag +(0009,"ACUSON",06) UN Unknown 1 PrivateTag +(0009,"ACUSON",07) UN Unknown 1 PrivateTag +(0009,"ACUSON",08) LT Unknown 1 PrivateTag +(0009,"ACUSON",09) LT Unknown 1 PrivateTag +(0009,"ACUSON",0a) IS Unknown 1 PrivateTag +(0009,"ACUSON",0b) IS Unknown 1 PrivateTag +(0009,"ACUSON",0c) IS Unknown 1 PrivateTag +(0009,"ACUSON",0d) IS Unknown 1 PrivateTag +(0009,"ACUSON",0e) IS Unknown 1 PrivateTag +(0009,"ACUSON",0f) UN Unknown 1 PrivateTag +(0009,"ACUSON",10) IS Unknown 1 PrivateTag +(0009,"ACUSON",11) UN Unknown 1 PrivateTag +(0009,"ACUSON",12) IS Unknown 1 PrivateTag +(0009,"ACUSON",13) IS Unknown 1 PrivateTag +(0009,"ACUSON",14) LT Unknown 1 PrivateTag +(0009,"ACUSON",15) UN Unknown 1 PrivateTag + +(0003,"AEGIS_DICOM_2.00",00) US Unknown 1-n PrivateTag +(0005,"AEGIS_DICOM_2.00",00) US Unknown 1-n PrivateTag +(0009,"AEGIS_DICOM_2.00",00) US Unknown 1-n PrivateTag +(0019,"AEGIS_DICOM_2.00",00) US Unknown 1-n PrivateTag +(0029,"AEGIS_DICOM_2.00",00) US Unknown 1-n PrivateTag +(1369,"AEGIS_DICOM_2.00",00) US Unknown 1-n PrivateTag + +(0009,"AGFA",10) LO Unknown 1 PrivateTag +(0009,"AGFA",11) LO Unknown 1 PrivateTag +(0009,"AGFA",13) LO Unknown 1 PrivateTag +(0009,"AGFA",14) LO Unknown 1 PrivateTag +(0009,"AGFA",15) LO Unknown 1 PrivateTag + +(0031,"AGFA PACS Archive Mirroring 1.0",00) CS StudyStatus 1 PrivateTag +(0031,"AGFA PACS Archive Mirroring 1.0",01) UL DateTimeVerified 1 PrivateTag + +(0029,"CAMTRONICS IP",10) LT Unknown 1 PrivateTag +(0029,"CAMTRONICS IP",20) UN Unknown 1 PrivateTag +(0029,"CAMTRONICS IP",30) UN Unknown 1 PrivateTag +(0029,"CAMTRONICS IP",40) UN Unknown 1 PrivateTag + +(0029,"CAMTRONICS",10) LT Commentline 1 PrivateTag +(0029,"CAMTRONICS",20) DS EdgeEnhancementCoefficient 1 PrivateTag +(0029,"CAMTRONICS",50) LT SceneText 1 PrivateTag +(0029,"CAMTRONICS",60) LT ImageText 1 PrivateTag +(0029,"CAMTRONICS",70) IS PixelShiftHorizontal 1 PrivateTag +(0029,"CAMTRONICS",80) IS PixelShiftVertical 1 PrivateTag +(0029,"CAMTRONICS",90) IS Unknown 1 PrivateTag + +(0009,"CARDIO-D.R. 1.0",00) UL FileLocation 1 PrivateTag +(0009,"CARDIO-D.R. 1.0",01) UL FileSize 1 PrivateTag +(0009,"CARDIO-D.R. 1.0",40) SQ AlternateImageSequence 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",00) CS ImageBlankingShape 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",02) IS ImageBlankingLeftVerticalEdge 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",04) IS ImageBlankingRightVerticalEdge 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",06) IS ImageBlankingUpperHorizontalEdge 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",08) IS ImageBlankingLowerHorizontalEdge 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",10) IS CenterOfCircularImageBlanking 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",12) IS RadiusOfCircularImageBlanking 1 PrivateTag +(0019,"CARDIO-D.R. 1.0",30) UL MaximumImageFrameSize 1 PrivateTag +(0021,"CARDIO-D.R. 1.0",13) IS ImageSequenceNumber 1 PrivateTag +(0029,"CARDIO-D.R. 1.0",00) SQ EdgeEnhancementSequence 1 PrivateTag +(0029,"CARDIO-D.R. 1.0",01) US ConvolutionKernelSize 2 PrivateTag +(0029,"CARDIO-D.R. 1.0",02) DS ConvolutionKernelCoefficients 1-n PrivateTag +(0029,"CARDIO-D.R. 1.0",03) DS EdgeEnhancementGain 1 PrivateTag + +(0025,"CMR42 CIRCLECVI",1010) LO WorkspaceID 1 PrivateTag +(0025,"CMR42 CIRCLECVI",1020) LO WorkspaceTimeString 1 PrivateTag +(0025,"CMR42 CIRCLECVI",1030) OB WorkspaceStream 1 PrivateTag + +(0009,"DCMTK_ANONYMIZER",00) SQ AnonymizerUIDMap 1 PrivateTag +(0009,"DCMTK_ANONYMIZER",10) UI AnonymizerUIDKey 1 PrivateTag +(0009,"DCMTK_ANONYMIZER",20) UI AnonymizerUIDValue 1 PrivateTag +(0009,"DCMTK_ANONYMIZER",30) SQ AnonymizerPatientIDMap 1 PrivateTag +(0009,"DCMTK_ANONYMIZER",40) LO AnonymizerPatientIDKey 1 PrivateTag +(0009,"DCMTK_ANONYMIZER",50) LO AnonymizerPatientIDValue 1 PrivateTag + +(0019,"DIDI TO PCR 1.1",22) UN RouteAET 1 PrivateTag +(0019,"DIDI TO PCR 1.1",23) DS PCRPrintScale 1 PrivateTag +(0019,"DIDI TO PCR 1.1",24) UN PCRPrintJobEnd 1 PrivateTag +(0019,"DIDI TO PCR 1.1",25) IS PCRNoFilmCopies 1 PrivateTag +(0019,"DIDI TO PCR 1.1",26) IS PCRFilmLayoutPosition 1 PrivateTag +(0019,"DIDI TO PCR 1.1",27) UN PCRPrintReportName 1 PrivateTag +(0019,"DIDI TO PCR 1.1",70) UN RADProtocolPrinter 1 PrivateTag +(0019,"DIDI TO PCR 1.1",71) UN RADProtocolMedium 1 PrivateTag +(0019,"DIDI TO PCR 1.1",90) LO UnprocessedFlag 1 PrivateTag +(0019,"DIDI TO PCR 1.1",91) UN KeyValues 1 PrivateTag +(0019,"DIDI TO PCR 1.1",92) UN DestinationPostprocessingFunction 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A0) UN Version 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A1) UN RangingMode 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A2) UN AbdomenBrightness 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A3) UN FixedBrightness 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A4) UN DetailContrast 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A5) UN ContrastBalance 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A6) UN StructureBoost 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A7) UN StructurePreference 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A8) UN NoiseRobustness 1 PrivateTag +(0019,"DIDI TO PCR 1.1",A9) UN NoiseDoseLimit 1 PrivateTag +(0019,"DIDI TO PCR 1.1",AA) UN NoiseDoseStep 1 PrivateTag +(0019,"DIDI TO PCR 1.1",AB) UN NoiseFrequencyLimit 1 PrivateTag +(0019,"DIDI TO PCR 1.1",AC) UN WeakContrastLimit 1 PrivateTag +(0019,"DIDI TO PCR 1.1",AD) UN StrongContrastLimit 1 PrivateTag +(0019,"DIDI TO PCR 1.1",AE) UN StructureBoostOffset 1 PrivateTag +(0019,"DIDI TO PCR 1.1",AF) UN SmoothGain 1 PrivateTag +(0019,"DIDI TO PCR 1.1",B0) UN MeasureField1 1 PrivateTag +(0019,"DIDI TO PCR 1.1",B1) UN MeasureField2 1 PrivateTag +(0019,"DIDI TO PCR 1.1",B2) UN KeyPercentile1 1 PrivateTag +(0019,"DIDI TO PCR 1.1",B3) UN KeyPercentile2 1 PrivateTag +(0019,"DIDI TO PCR 1.1",B4) UN DensityLUT 1 PrivateTag +(0019,"DIDI TO PCR 1.1",B5) UN Brightness 1 PrivateTag +(0019,"DIDI TO PCR 1.1",B6) UN Gamma 1 PrivateTag +(0089,"DIDI TO PCR 1.1",10) SQ Unknown 1 PrivateTag + +(0029,"DIGISCAN IMAGE",31) US Unknown 1-n PrivateTag +(0029,"DIGISCAN IMAGE",32) US Unknown 1-n PrivateTag +(0029,"DIGISCAN IMAGE",33) LT Unknown 1 PrivateTag +(0029,"DIGISCAN IMAGE",34) LT Unknown 1 PrivateTag + +(7001-o-70ff,"DLX_ANNOT_01",04) ST TextAnnotation 1 PrivateTag +(7001-o-70ff,"DLX_ANNOT_01",05) IS Box 2 PrivateTag +(7001-o-70ff,"DLX_ANNOT_01",07) IS ArrowEnd 2 PrivateTag + +(0015,"DLX_EXAMS_01",01) DS StenosisCalibrationRatio 1 PrivateTag +(0015,"DLX_EXAMS_01",02) DS StenosisMagnification 1 PrivateTag +(0015,"DLX_EXAMS_01",03) DS CardiacCalibrationRatio 1 PrivateTag + +(6001-o-60ff,"DLX_LKUP_01",01) US GrayPaletteColorLookupTableDescriptor 3 PrivateTag +(6001-o-60ff,"DLX_LKUP_01",02) US GrayPaletteColorLookupTableData 1 PrivateTag + +(0011,"DLX_PATNT_01",01) LT PatientDOB 1 PrivateTag + +(0019,"DLX_SERIE_01",01) DS AngleValueLArm 1 PrivateTag +(0019,"DLX_SERIE_01",02) DS AngleValuePArm 1 PrivateTag +(0019,"DLX_SERIE_01",03) DS AngleValueCArm 1 PrivateTag +(0019,"DLX_SERIE_01",04) CS AngleLabelLArm 1 PrivateTag +(0019,"DLX_SERIE_01",05) CS AngleLabelPArm 1 PrivateTag +(0019,"DLX_SERIE_01",06) CS AngleLabelCArm 1 PrivateTag +(0019,"DLX_SERIE_01",07) ST ProcedureName 1 PrivateTag +(0019,"DLX_SERIE_01",08) ST ExamName 1 PrivateTag +(0019,"DLX_SERIE_01",09) SH PatientSize 1 PrivateTag +(0019,"DLX_SERIE_01",0a) IS RecordView 1 PrivateTag +(0019,"DLX_SERIE_01",10) DS InjectorDelay 1 PrivateTag +(0019,"DLX_SERIE_01",11) CS AutoInject 1 PrivateTag +(0019,"DLX_SERIE_01",14) IS AcquisitionMode 1 PrivateTag +(0019,"DLX_SERIE_01",15) CS CameraRotationEnabled 1 PrivateTag +(0019,"DLX_SERIE_01",16) CS ReverseSweep 1 PrivateTag +(0019,"DLX_SERIE_01",17) IS SpatialFilterStrength 1 PrivateTag +(0019,"DLX_SERIE_01",18) IS ZoomFactor 1 PrivateTag +(0019,"DLX_SERIE_01",19) IS XZoomCenter 1 PrivateTag +(0019,"DLX_SERIE_01",1a) IS YZoomCenter 1 PrivateTag +(0019,"DLX_SERIE_01",1b) DS Focus 1 PrivateTag +(0019,"DLX_SERIE_01",1c) CS Dose 1 PrivateTag +(0019,"DLX_SERIE_01",1d) IS SideMark 1 PrivateTag +(0019,"DLX_SERIE_01",1e) IS PercentageLandscape 1 PrivateTag +(0019,"DLX_SERIE_01",1f) DS ExposureDuration 1 PrivateTag + +(00E1,"ELSCINT1",01) US DataDictionaryVersion 1 PrivateTag +(00E1,"ELSCINT1",14) LT Unknown 1 PrivateTag +(00E1,"ELSCINT1",22) DS Unknown 2 PrivateTag +(00E1,"ELSCINT1",23) DS Unknown 2 PrivateTag +(00E1,"ELSCINT1",24) LT Unknown 1 PrivateTag +(00E1,"ELSCINT1",25) LT Unknown 1 PrivateTag +(00E1,"ELSCINT1",40) SH OffsetFromCTMRImages 1 PrivateTag +(0601,"ELSCINT1",00) SH ImplementationVersion 1 PrivateTag +(0601,"ELSCINT1",20) DS RelativeTablePosition 1 PrivateTag +(0601,"ELSCINT1",21) DS RelativeTableHeight 1 PrivateTag +(0601,"ELSCINT1",30) SH SurviewDirection 1 PrivateTag +(0601,"ELSCINT1",31) DS SurviewLength 1 PrivateTag +(0601,"ELSCINT1",50) SH ImageViewType 1 PrivateTag +(0601,"ELSCINT1",70) DS BatchNumber 1 PrivateTag +(0601,"ELSCINT1",71) DS BatchSize 1 PrivateTag +(0601,"ELSCINT1",72) DS BatchSliceNumber 1 PrivateTag + +(0009,"FDMS 1.0",04) SH ImageControlUnit 1 PrivateTag +(0009,"FDMS 1.0",05) OW ImageUID 1 PrivateTag +(0009,"FDMS 1.0",06) OW RouteImageUID 1 PrivateTag +(0009,"FDMS 1.0",08) UL ImageDisplayInformationVersionNo 1 PrivateTag +(0009,"FDMS 1.0",09) UL PatientInformationVersionNo 1 PrivateTag +(0009,"FDMS 1.0",0C) OW FilmUID 1 PrivateTag +(0009,"FDMS 1.0",10) CS ExposureUnitTypeCode 1 PrivateTag +(0009,"FDMS 1.0",80) LO KanjiHospitalName 1 PrivateTag +(0009,"FDMS 1.0",90) ST DistributionCode 1 PrivateTag +(0009,"FDMS 1.0",92) SH KanjiDepartmentName 1 PrivateTag +(0009,"FDMS 1.0",F0) CS BlackeningProcessFlag 1 PrivateTag +(0019,"FDMS 1.0",15) LO KanjiBodyPartForExposure 1 PrivateTag +(0019,"FDMS 1.0",32) LO KanjiMenuName 1 PrivateTag +(0019,"FDMS 1.0",40) CS ImageProcessingType 1 PrivateTag +(0019,"FDMS 1.0",50) CS EDRMode 1 PrivateTag +(0019,"FDMS 1.0",60) SH RadiographersCode 1 PrivateTag +(0019,"FDMS 1.0",70) IS SplitExposureFormat 1 PrivateTag +(0019,"FDMS 1.0",71) IS NoOfSplitExposureFrames 1 PrivateTag +(0019,"FDMS 1.0",80) IS ReadingPositionSpecification 1 PrivateTag +(0019,"FDMS 1.0",81) IS ReadingSensitivityCenter 1 PrivateTag +(0019,"FDMS 1.0",90) SH FilmAnnotationCharacterString1 1 PrivateTag +(0019,"FDMS 1.0",91) SH FilmAnnotationCharacterString2 1 PrivateTag +(0021,"FDMS 1.0",10) CS FCRImageID 1 PrivateTag +(0021,"FDMS 1.0",30) CS SetNo 1 PrivateTag +(0021,"FDMS 1.0",40) IS ImageNoInTheSet 1 PrivateTag +(0021,"FDMS 1.0",50) CS PairProcessingInformation 1 PrivateTag +(0021,"FDMS 1.0",80) OB EquipmentTypeSpecificInformation 1 PrivateTag +(0023,"FDMS 1.0",10) SQ Unknown 1 PrivateTag +(0023,"FDMS 1.0",20) SQ Unknown 1 PrivateTag +(0023,"FDMS 1.0",30) SQ Unknown 1 PrivateTag +(0025,"FDMS 1.0",10) US RelativeLightEmissionAmountSk 1 PrivateTag +(0025,"FDMS 1.0",11) US TermOfCorrectionForEachIPTypeSt 1 PrivateTag +(0025,"FDMS 1.0",12) US ReadingGainGp 1 PrivateTag +(0025,"FDMS 1.0",13) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",15) CS Unknown 1 PrivateTag +(0025,"FDMS 1.0",20) US Unknown 2 PrivateTag +(0025,"FDMS 1.0",21) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",30) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",31) SS Unknown 1 PrivateTag +(0025,"FDMS 1.0",32) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",33) SS Unknown 1 PrivateTag +(0025,"FDMS 1.0",34) SS Unknown 1 PrivateTag +(0025,"FDMS 1.0",40) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",41) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",42) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",43) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",50) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",51) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",52) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",53) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",60) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",61) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",62) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",63) CS Unknown 1 PrivateTag +(0025,"FDMS 1.0",70) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",71) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",72) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",73) US Unknown 1-n PrivateTag +(0025,"FDMS 1.0",74) US Unknown 1-n PrivateTag +(0025,"FDMS 1.0",80) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",81) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",82) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",83) US Unknown 1-n PrivateTag +(0025,"FDMS 1.0",84) US Unknown 1-n PrivateTag +(0025,"FDMS 1.0",90) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",91) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",92) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",93) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",94) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",95) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",96) CS Unknown 1 PrivateTag +(0025,"FDMS 1.0",a0) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",a1) SS Unknown 1 PrivateTag +(0025,"FDMS 1.0",a2) US Unknown 1 PrivateTag +(0025,"FDMS 1.0",a3) SS Unknown 1 PrivateTag +(0027,"FDMS 1.0",10) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",20) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",30) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",40) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",50) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",60) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",70) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",80) SQ Unknown 1 PrivateTag +(0027,"FDMS 1.0",a0) IS Unknown 1 PrivateTag +(0027,"FDMS 1.0",a1) CS Unknown 2 PrivateTag +(0027,"FDMS 1.0",a2) CS Unknown 2 PrivateTag +(0027,"FDMS 1.0",a3) SS Unknown 1-n PrivateTag +(0029,"FDMS 1.0",20) CS ImageScanningDirection 1 PrivateTag +(0029,"FDMS 1.0",30) CS ExtendedReadingSizeValue 1 PrivateTag +(0029,"FDMS 1.0",34) US MagnificationReductionRatio 1 PrivateTag +(0029,"FDMS 1.0",44) CS LineDensityCode 1 PrivateTag +(0029,"FDMS 1.0",50) CS DataCompressionCode 1 PrivateTag +(2011,"FDMS 1.0",11) CS ImagePosition SpecifyingFlag 1 PrivateTag +(50F1,"FDMS 1.0",06) CS EnergySubtractionParam 1 PrivateTag +(50F1,"FDMS 1.0",07) CS SubtractionRegistrationResult 1 PrivateTag +(50F1,"FDMS 1.0",08) CS EnergySubtractionParam2 1 PrivateTag +(50F1,"FDMS 1.0",09) SL AfinConversionCoefficient 1 PrivateTag +(50F1,"FDMS 1.0",10) CS FilmOutputFormat 1 PrivateTag +(50F1,"FDMS 1.0",20) CS ImageProcessingModificationFlag 1 PrivateTag + +(0009,"FFP DATA",01) UN CRHeaderInformation 1 PrivateTag + +(0019,"GE ??? From Adantage Review CS",30) LO CREDRMode 1 PrivateTag +(0019,"GE ??? From Adantage Review CS",40) LO CRLatitude 1 PrivateTag +(0019,"GE ??? From Adantage Review CS",50) LO CRGroupNumber 1 PrivateTag +(0019,"GE ??? From Adantage Review CS",70) LO CRImageSerialNumber 1 PrivateTag +(0019,"GE ??? From Adantage Review CS",80) LO CRBarCodeNumber 1 PrivateTag +(0019,"GE ??? From Adantage Review CS",90) LO CRFilmOutputExposures 1 PrivateTag + +(0009,"GEMS_ACQU_01",24) DS Unknown 1 PrivateTag +(0009,"GEMS_ACQU_01",25) US Unknown 1 PrivateTag +(0009,"GEMS_ACQU_01",3e) US Unknown 1 PrivateTag +(0009,"GEMS_ACQU_01",3f) US Unknown 1 PrivateTag +(0009,"GEMS_ACQU_01",42) US Unknown 1 PrivateTag +(0009,"GEMS_ACQU_01",43) US Unknown 1 PrivateTag +(0009,"GEMS_ACQU_01",f8) US Unknown 1 PrivateTag +(0009,"GEMS_ACQU_01",fb) IS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",01) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",02) SL NumberOfCellsInDetector 1 PrivateTag +(0019,"GEMS_ACQU_01",03) DS CellNumberAtTheta 1 PrivateTag +(0019,"GEMS_ACQU_01",04) DS CellSpacing 1 PrivateTag +(0019,"GEMS_ACQU_01",05) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",06) UN Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",0e) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",0f) DS HorizontalFrameOfReference 1 PrivateTag +(0019,"GEMS_ACQU_01",11) SS SeriesContrast 1 PrivateTag +(0019,"GEMS_ACQU_01",12) SS LastPseq 1 PrivateTag +(0019,"GEMS_ACQU_01",13) SS StartNumberForBaseline 1 PrivateTag +(0019,"GEMS_ACQU_01",14) SS End NumberForBaseline 1 PrivateTag +(0019,"GEMS_ACQU_01",15) SS StartNumberForEnhancedScans 1 PrivateTag +(0019,"GEMS_ACQU_01",16) SS EndNumberForEnhancedScans 1 PrivateTag +(0019,"GEMS_ACQU_01",17) SS SeriesPlane 1 PrivateTag +(0019,"GEMS_ACQU_01",18) LO FirstScanRAS 1 PrivateTag +(0019,"GEMS_ACQU_01",19) DS FirstScanLocation 1 PrivateTag +(0019,"GEMS_ACQU_01",1a) LO LastScanRAS 1 PrivateTag +(0019,"GEMS_ACQU_01",1b) DS LastScanLocation 1 PrivateTag +(0019,"GEMS_ACQU_01",1e) DS DisplayFieldOfView 1 PrivateTag +(0019,"GEMS_ACQU_01",20) DS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",22) DS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",23) DS TableSpeed 1 PrivateTag +(0019,"GEMS_ACQU_01",24) DS MidScanTime 1 PrivateTag +(0019,"GEMS_ACQU_01",25) SS MidScanFlag 1 PrivateTag +(0019,"GEMS_ACQU_01",26) SL DegreesOfAzimuth 1 PrivateTag +(0019,"GEMS_ACQU_01",27) DS GantryPeriod 1 PrivateTag +(0019,"GEMS_ACQU_01",2a) DS XrayOnPosition 1 PrivateTag +(0019,"GEMS_ACQU_01",2b) DS XrayOffPosition 1 PrivateTag +(0019,"GEMS_ACQU_01",2c) SL NumberOfTriggers 1 PrivateTag +(0019,"GEMS_ACQU_01",2d) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",2e) DS AngleOfFirstView 1 PrivateTag +(0019,"GEMS_ACQU_01",2f) DS TriggerFrequency 1 PrivateTag +(0019,"GEMS_ACQU_01",39) SS ScanFOVType 1 PrivateTag +(0019,"GEMS_ACQU_01",3a) IS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",3b) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",3c) UN Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",3e) UN Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",3f) UN Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",40) SS StatReconFlag 1 PrivateTag +(0019,"GEMS_ACQU_01",41) SS ComputeType 1 PrivateTag +(0019,"GEMS_ACQU_01",42) SS SegmentNumber 1 PrivateTag +(0019,"GEMS_ACQU_01",43) SS TotalSegmentsRequested 1 PrivateTag +(0019,"GEMS_ACQU_01",44) DS InterscanDelay 1 PrivateTag +(0019,"GEMS_ACQU_01",47) SS ViewCompressionFactor 1 PrivateTag +(0019,"GEMS_ACQU_01",48) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",49) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",4a) SS TotalNumberOfRefChannels 1 PrivateTag +(0019,"GEMS_ACQU_01",4b) SL DataSizeForScanData 1 PrivateTag +(0019,"GEMS_ACQU_01",52) SS ReconPostProcessingFlag 1 PrivateTag +(0019,"GEMS_ACQU_01",54) UN Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",57) SS CTWaterNumber 1 PrivateTag +(0019,"GEMS_ACQU_01",58) SS CTBoneNumber 1 PrivateTag +(0019,"GEMS_ACQU_01",5a) FL AcquisitionDuration 1 PrivateTag +(0019,"GEMS_ACQU_01",5d) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",5e) SL NumberOfChannels1To512 1 PrivateTag +(0019,"GEMS_ACQU_01",5f) SL IncrementBetweenChannels 1 PrivateTag +(0019,"GEMS_ACQU_01",60) SL StartingView 1 PrivateTag +(0019,"GEMS_ACQU_01",61) SL NumberOfViews 1 PrivateTag +(0019,"GEMS_ACQU_01",62) SL IncrementBetweenViews 1 PrivateTag +(0019,"GEMS_ACQU_01",6a) SS DependantOnNumberOfViewsProcessed 1 PrivateTag +(0019,"GEMS_ACQU_01",6b) SS FieldOfViewInDetectorCells 1 PrivateTag +(0019,"GEMS_ACQU_01",70) SS ValueOfBackProjectionButton 1 PrivateTag +(0019,"GEMS_ACQU_01",71) SS SetIfFatqEstimatesWereUsed 1 PrivateTag +(0019,"GEMS_ACQU_01",72) DS ZChannelAvgOverViews 1 PrivateTag +(0019,"GEMS_ACQU_01",73) DS AvgOfLeftRefChannelsOverViews 1 PrivateTag +(0019,"GEMS_ACQU_01",74) DS MaxLeftChannelOverViews 1 PrivateTag +(0019,"GEMS_ACQU_01",75) DS AvgOfRightRefChannelsOverViews 1 PrivateTag +(0019,"GEMS_ACQU_01",76) DS MaxRightChannelOverViews 1 PrivateTag +(0019,"GEMS_ACQU_01",7d) DS SecondEcho 1 PrivateTag +(0019,"GEMS_ACQU_01",7e) SS NumberOfEchos 1 PrivateTag +(0019,"GEMS_ACQU_01",7f) DS TableDelta 1 PrivateTag +(0019,"GEMS_ACQU_01",81) SS Contiguous 1 PrivateTag +(0019,"GEMS_ACQU_01",82) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",83) DS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",84) DS PeakSAR 1 PrivateTag +(0019,"GEMS_ACQU_01",85) SS MonitorSAR 1 PrivateTag +(0019,"GEMS_ACQU_01",86) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",87) DS CardiacRepetition Time 1 PrivateTag +(0019,"GEMS_ACQU_01",88) SS ImagesPerCardiacCycle 1 PrivateTag +(0019,"GEMS_ACQU_01",8a) SS ActualReceiveGainAnalog 1 PrivateTag +(0019,"GEMS_ACQU_01",8b) SS ActualReceiveGainDigital 1 PrivateTag +(0019,"GEMS_ACQU_01",8d) DS DelayAfterTrigger 1 PrivateTag +(0019,"GEMS_ACQU_01",8f) SS SwapPhaseFrequency 1 PrivateTag +(0019,"GEMS_ACQU_01",90) SS PauseInterval 1 PrivateTag +(0019,"GEMS_ACQU_01",91) DS PulseTime 1 PrivateTag +(0019,"GEMS_ACQU_01",92) SL SliceOffsetOnFrequencyAxis 1 PrivateTag +(0019,"GEMS_ACQU_01",93) DS CenterFrequency 1 PrivateTag +(0019,"GEMS_ACQU_01",94) SS TransmitGain 1 PrivateTag +(0019,"GEMS_ACQU_01",95) SS AnalogReceiverGain 1 PrivateTag +(0019,"GEMS_ACQU_01",96) SS DigitalReceiverGain 1 PrivateTag +(0019,"GEMS_ACQU_01",97) SL BitmapDefiningCVs 1 PrivateTag +(0019,"GEMS_ACQU_01",98) SS CenterFrequencyMethod 1 PrivateTag +(0019,"GEMS_ACQU_01",99) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",9b) SS PulseSequenceMode 1 PrivateTag +(0019,"GEMS_ACQU_01",9c) LO PulseSequenceName 1 PrivateTag +(0019,"GEMS_ACQU_01",9d) DT PulseSequenceDate 1 PrivateTag +(0019,"GEMS_ACQU_01",9e) LO InternalPulseSequenceName 1 PrivateTag +(0019,"GEMS_ACQU_01",9f) SS TransmittingCoil 1 PrivateTag +(0019,"GEMS_ACQU_01",a0) SS SurfaceCoilType 1 PrivateTag +(0019,"GEMS_ACQU_01",a1) SS ExtremityCoilFlag 1 PrivateTag +(0019,"GEMS_ACQU_01",a2) SL RawDataRunNumber 1 PrivateTag +(0019,"GEMS_ACQU_01",a3) UL CalibratedFieldStrength 1 PrivateTag +(0019,"GEMS_ACQU_01",a4) SS SATFatWaterBone 1 PrivateTag +(0019,"GEMS_ACQU_01",a5) DS ReceiveBandwidth 1 PrivateTag +(0019,"GEMS_ACQU_01",a7) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",a8) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",a9) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",aa) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",ab) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",ac) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",ad) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",ae) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",af) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b0) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b1) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b2) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b3) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b4) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b5) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b6) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b7) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b8) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",b9) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",ba) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",bb) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",bc) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",bd) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",be) DS ProjectionAngle 1 PrivateTag +(0019,"GEMS_ACQU_01",c0) SS SaturationPlanes 1 PrivateTag +(0019,"GEMS_ACQU_01",c1) SS SurfaceCoilIntensityCorrectionFlag 1 PrivateTag +(0019,"GEMS_ACQU_01",c2) SS SATLocationR 1 PrivateTag +(0019,"GEMS_ACQU_01",c3) SS SATLocationL 1 PrivateTag +(0019,"GEMS_ACQU_01",c4) SS SATLocationA 1 PrivateTag +(0019,"GEMS_ACQU_01",c5) SS SATLocationP 1 PrivateTag +(0019,"GEMS_ACQU_01",c6) SS SATLocationH 1 PrivateTag +(0019,"GEMS_ACQU_01",c7) SS SATLocationF 1 PrivateTag +(0019,"GEMS_ACQU_01",c8) SS SATThicknessRL 1 PrivateTag +(0019,"GEMS_ACQU_01",c9) SS SATThicknessAP 1 PrivateTag +(0019,"GEMS_ACQU_01",ca) SS SATThicknessHF 1 PrivateTag +(0019,"GEMS_ACQU_01",cb) SS PrescribedFlowAxis 1 PrivateTag +(0019,"GEMS_ACQU_01",cc) SS VelocityEncoding 1 PrivateTag +(0019,"GEMS_ACQU_01",cd) SS ThicknessDisclaimer 1 PrivateTag +(0019,"GEMS_ACQU_01",ce) SS PrescanType 1 PrivateTag +(0019,"GEMS_ACQU_01",cf) SS PrescanStatus 1 PrivateTag +(0019,"GEMS_ACQU_01",d0) SH RawDataType 1 PrivateTag +(0019,"GEMS_ACQU_01",d2) SS ProjectionAlgorithm 1 PrivateTag +(0019,"GEMS_ACQU_01",d3) SH ProjectionAlgorithm 1 PrivateTag +(0019,"GEMS_ACQU_01",d4) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",d5) SS FractionalEcho 1 PrivateTag +(0019,"GEMS_ACQU_01",d6) SS PrepPulse 1 PrivateTag +(0019,"GEMS_ACQU_01",d7) SS CardiacPhases 1 PrivateTag +(0019,"GEMS_ACQU_01",d8) SS VariableEchoFlag 1 PrivateTag +(0019,"GEMS_ACQU_01",d9) DS ConcatenatedSAT 1 PrivateTag +(0019,"GEMS_ACQU_01",da) SS ReferenceChannelUsed 1 PrivateTag +(0019,"GEMS_ACQU_01",db) DS BackProjectorCoefficient 1 PrivateTag +(0019,"GEMS_ACQU_01",dc) SS PrimarySpeedCorrectionUsed 1 PrivateTag +(0019,"GEMS_ACQU_01",dd) SS OverrangeCorrectionUsed 1 PrivateTag +(0019,"GEMS_ACQU_01",de) DS DynamicZAlphaValue 1 PrivateTag +(0019,"GEMS_ACQU_01",df) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",e0) DS UserData 1 PrivateTag +(0019,"GEMS_ACQU_01",e1) DS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",e2) DS VelocityEncodeScale 1 PrivateTag +(0019,"GEMS_ACQU_01",e3) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",e4) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",e5) IS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",e6) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",e8) DS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",e9) DS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",eb) DS Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",ec) US Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",f0) UN Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",f1) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",f2) SS FastPhases 1 PrivateTag +(0019,"GEMS_ACQU_01",f3) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",f4) LT Unknown 1 PrivateTag +(0019,"GEMS_ACQU_01",f9) DS TransmitGain 1 PrivateTag + +(0023,"GEMS_ACRQA_1.0 BLOCK1",00) LO CRExposureMenuCode 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",10) LO CRExposureMenuString 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",20) LO CREDRMode 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",30) LO CRLatitude 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",40) LO CRGroupNumber 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",50) US CRImageSerialNumber 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",60) LO CRBarCodeNumber 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",70) LO CRFilmOutputExposure 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",80) LO CRFilmFormat 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK1",90) LO CRSShiftString 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",00) US CRSShift 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",10) DS CRCShift 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",20) DS CRGT 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",30) DS CRGA 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",40) DS CRGC 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",50) DS CRGS 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",60) DS CRRT 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",70) DS CRRE 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",80) US CRRN 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK2",90) DS CRDRT 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",00) DS CRDRE 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",10) US CRDRN 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",20) DS CRORE 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",30) US CRORN 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",40) US CRORD 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",50) LO CRCassetteSize 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",60) LO CRMachineID 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",70) LO CRMachineType 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",80) LO CRTechnicianCode 1 PrivateTag +(0023,"GEMS_ACRQA_1.0 BLOCK3",90) LO CREnergySubtractionParameters 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",00) LO CRExposureMenuCode 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",10) LO CRExposureMenuString 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",20) LO CREDRMode 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",30) LO CRLatitude 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",40) LO CRGroupNumber 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",50) US CRImageSerialNumber 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",60) LO CRBarCodeNumber 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",70) LO CRFilmOutputExposure 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",80) LO CRFilmFormat 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK1",90) LO CRSShiftString 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",00) US CRSShift 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",10) LO CRCShift 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",20) LO CRGT 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",30) DS CRGA 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",40) DS CRGC 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",50) DS CRGS 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",60) LO CRRT 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",70) DS CRRE 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",80) US CRRN 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK2",90) DS CRDRT 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",00) DS CRDRE 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",10) US CRDRN 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",20) DS CRORE 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",30) US CRORN 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",40) US CRORD 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",50) LO CRCassetteSize 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",60) LO CRMachineID 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",70) LO CRMachineType 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",80) LO CRTechnicianCode 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",90) LO CREnergySubtractionParameters 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",f0) LO CRDistributionCode 1 PrivateTag +(0023,"GEMS_ACRQA_2.0 BLOCK3",ff) US CRShuttersApplied 1 PrivateTag + +(0047,"GEMS_ADWSoft_3D1",01) SQ Reconstruction Parameters Sequence 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",50) UL VolumeVoxelCount 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",51) UL VolumeSegmentCount 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",53) US VolumeSliceSize 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",54) US VolumeSliceCount 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",55) SL VolumeThresholdValue 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",57) DS VolumeVoxelRatio 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",58) DS VolumeVoxelSize 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",59) US VolumeZPositionSize 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",60) DS VolumeBaseLine 9 PrivateTag +(0047,"GEMS_ADWSoft_3D1",61) DS VolumeCenterPoint 3 PrivateTag +(0047,"GEMS_ADWSoft_3D1",63) SL VolumeSkewBase 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",64) DS VolumeRegistrationTransformRotationMatrix 9 PrivateTag +(0047,"GEMS_ADWSoft_3D1",65) DS VolumeRegistrationTransformTranslationVector 3 PrivateTag +(0047,"GEMS_ADWSoft_3D1",70) DS KVPList 1-n PrivateTag +(0047,"GEMS_ADWSoft_3D1",71) IS XRayTubeCurrentList 1-n PrivateTag +(0047,"GEMS_ADWSoft_3D1",72) IS ExposureList 1-n PrivateTag +(0047,"GEMS_ADWSoft_3D1",80) LO AcquisitionDLXIdentifier 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",85) SQ AcquisitionDLX2DSeriesSequence 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",89) DS ContrastAgentVolumeList 1-n PrivateTag +(0047,"GEMS_ADWSoft_3D1",8A) US NumberOfInjections 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",8B) US FrameCount 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",91) LO XA3DReconstructionAlgorithmName 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",92) CS XA3DReconstructionAlgorithmVersion 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",93) DA DLXCalibrationDate 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",94) TM DLXCalibrationTime 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",95) CS DLXCalibrationStatus 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",96) IS UsedFrames 1-n PrivateTag +(0047,"GEMS_ADWSoft_3D1",98) US TransformCount 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",99) SQ TransformSequence 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",9A) DS TransformRotationMatrix 9 PrivateTag +(0047,"GEMS_ADWSoft_3D1",9B) DS TransformTranslationVector 3 PrivateTag +(0047,"GEMS_ADWSoft_3D1",9C) LO TransformLabel 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B0) SQ WireframeList 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B1) US WireframeCount 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B2) US LocationSystem 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B5) LO WireframeName 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B6) LO WireframeGroupName 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B7) LO WireframeColor 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B8) SL WireframeAttributes 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",B9) SL WireframePointCount 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",BA) SL WireframeTimestamp 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",BB) SQ WireframePointList 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",BC) DS WireframePointsCoordinates 3 PrivateTag +(0047,"GEMS_ADWSoft_3D1",C0) DS VolumeUpperLeftHighCornerRAS 3 PrivateTag +(0047,"GEMS_ADWSoft_3D1",C1) DS VolumeSliceToRASRotationMatrix 9 PrivateTag +(0047,"GEMS_ADWSoft_3D1",C2) DS VolumeUpperLeftHighCornerTLOC 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",D1) OB VolumeSegmentList 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",D2) OB VolumeGradientList 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",D3) OB VolumeDensityList 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",D4) OB VolumeZPositionList 1 PrivateTag +(0047,"GEMS_ADWSoft_3D1",D5) OB VolumeOriginalIndexList 1 PrivateTag +(0039,"GEMS_ADWSoft_DPO",80) IS PrivateEntityNumber 1 PrivateTag +(0039,"GEMS_ADWSoft_DPO",85) DA PrivateEntityDate 1 PrivateTag +(0039,"GEMS_ADWSoft_DPO",90) TM PrivateEntityTime 1 PrivateTag +(0039,"GEMS_ADWSoft_DPO",95) LO PrivateEntityLaunchCommand 1 PrivateTag +(0039,"GEMS_ADWSoft_DPO",AA) CS PrivateEntityType 1 PrivateTag + +(0033,"GEMS_CTHD_01",02) UN Unknown 1 PrivateTag + +(0037,"GEMS_DRS_1",10) LO ReferringDepartment 1 PrivateTag +(0037,"GEMS_DRS_1",20) US ScreenNumber 1 PrivateTag +(0037,"GEMS_DRS_1",40) SH LeftOrientation 1 PrivateTag +(0037,"GEMS_DRS_1",42) SH RightOrientation 1 PrivateTag +(0037,"GEMS_DRS_1",50) CS Inversion 1 PrivateTag +(0037,"GEMS_DRS_1",60) US DSA 1 PrivateTag + +(0009,"GEMS_GENIE_1",10) LO Unknown 1 PrivateTag +(0009,"GEMS_GENIE_1",11) SL StudyFlags 1 PrivateTag +(0009,"GEMS_GENIE_1",12) SL StudyType 1 PrivateTag +(0009,"GEMS_GENIE_1",1e) UI Unknown 1 PrivateTag +(0009,"GEMS_GENIE_1",20) LO Unknown 1 PrivateTag +(0009,"GEMS_GENIE_1",21) SL SeriesFlags 1 PrivateTag +(0009,"GEMS_GENIE_1",22) SH UserOrientation 1 PrivateTag +(0009,"GEMS_GENIE_1",23) SL InitiationType 1 PrivateTag +(0009,"GEMS_GENIE_1",24) SL InitiationDelay 1 PrivateTag +(0009,"GEMS_GENIE_1",25) SL InitiationCountRate 1 PrivateTag +(0009,"GEMS_GENIE_1",26) SL NumberEnergySets 1 PrivateTag +(0009,"GEMS_GENIE_1",27) SL NumberDetectors 1 PrivateTag +(0009,"GEMS_GENIE_1",29) SL Unknown 1 PrivateTag +(0009,"GEMS_GENIE_1",2a) SL Unknown 1 PrivateTag +(0009,"GEMS_GENIE_1",2c) LO SeriesComments 1 PrivateTag +(0009,"GEMS_GENIE_1",2d) SL TrackBeatAverage 1 PrivateTag +(0009,"GEMS_GENIE_1",2e) FD DistancePrescribed 1 PrivateTag +(0009,"GEMS_GENIE_1",30) LO Unknown 1 PrivateTag +(0009,"GEMS_GENIE_1",35) SL GantryLocusType 1 PrivateTag +(0009,"GEMS_GENIE_1",37) SL StartingHeartRate 1 PrivateTag +(0009,"GEMS_GENIE_1",38) SL RRWindowWidth 1 PrivateTag +(0009,"GEMS_GENIE_1",39) SL RRWindowOffset 1 PrivateTag +(0009,"GEMS_GENIE_1",3a) SL PercentCycleImaged 1 PrivateTag +(0009,"GEMS_GENIE_1",40) LO Unknown 1 PrivateTag +(0009,"GEMS_GENIE_1",41) SL PatientFlags 1 PrivateTag +(0009,"GEMS_GENIE_1",42) DA PatientCreationDate 1 PrivateTag +(0009,"GEMS_GENIE_1",43) TM PatientCreationTime 1 PrivateTag +(0011,"GEMS_GENIE_1",0a) SL SeriesType 1 PrivateTag +(0011,"GEMS_GENIE_1",0b) SL EffectiveSeriesDuration 1 PrivateTag +(0011,"GEMS_GENIE_1",0c) SL NumBeats 1 PrivateTag +(0011,"GEMS_GENIE_1",0d) LO RadioNuclideName 1 PrivateTag +(0011,"GEMS_GENIE_1",10) LO Unknown 1 PrivateTag +(0011,"GEMS_GENIE_1",12) LO DatasetName 1 PrivateTag +(0011,"GEMS_GENIE_1",13) SL DatasetType 1 PrivateTag +(0011,"GEMS_GENIE_1",15) SL DetectorNumber 1 PrivateTag +(0011,"GEMS_GENIE_1",16) SL EnergyNumber 1 PrivateTag +(0011,"GEMS_GENIE_1",17) SL RRIntervalWindowNumber 1 PrivateTag +(0011,"GEMS_GENIE_1",18) SL MGBinNumber 1 PrivateTag +(0011,"GEMS_GENIE_1",19) FD RadiusOfRotation 1 PrivateTag +(0011,"GEMS_GENIE_1",1a) SL DetectorCountZone 1 PrivateTag +(0011,"GEMS_GENIE_1",1b) SL NumEnergyWindows 1 PrivateTag +(0011,"GEMS_GENIE_1",1c) SL EnergyOffset 4 PrivateTag +(0011,"GEMS_GENIE_1",1d) SL EnergyRange 1 PrivateTag +(0011,"GEMS_GENIE_1",1f) SL ImageOrientation 1 PrivateTag +(0011,"GEMS_GENIE_1",23) SL UseFOVMask 1 PrivateTag +(0011,"GEMS_GENIE_1",24) SL FOVMaskYCutoffAngle 1 PrivateTag +(0011,"GEMS_GENIE_1",25) SL FOVMaskCutoffAngle 1 PrivateTag +(0011,"GEMS_GENIE_1",26) SL TableOrientation 1 PrivateTag +(0011,"GEMS_GENIE_1",27) SL ROITopLeft 2 PrivateTag +(0011,"GEMS_GENIE_1",28) SL ROIBottomRight 2 PrivateTag +(0011,"GEMS_GENIE_1",30) LO Unknown 1 PrivateTag +(0011,"GEMS_GENIE_1",33) LO EnergyCorrectName 1 PrivateTag +(0011,"GEMS_GENIE_1",34) LO SpatialCorrectName 1 PrivateTag +(0011,"GEMS_GENIE_1",35) LO TuningCalibName 1 PrivateTag +(0011,"GEMS_GENIE_1",36) LO UniformityCorrectName 1 PrivateTag +(0011,"GEMS_GENIE_1",37) LO AcquisitionSpecificCorrectName 1 PrivateTag +(0011,"GEMS_GENIE_1",38) SL ByteOrder 1 PrivateTag +(0011,"GEMS_GENIE_1",3a) SL PictureFormat 1 PrivateTag +(0011,"GEMS_GENIE_1",3b) FD PixelScale 1 PrivateTag +(0011,"GEMS_GENIE_1",3c) FD PixelOffset 1 PrivateTag +(0011,"GEMS_GENIE_1",3e) SL FOVShape 1 PrivateTag +(0011,"GEMS_GENIE_1",3f) SL DatasetFlags 1 PrivateTag +(0011,"GEMS_GENIE_1",44) FD ThresholdCenter 1 PrivateTag +(0011,"GEMS_GENIE_1",45) FD ThresholdWidth 1 PrivateTag +(0011,"GEMS_GENIE_1",46) SL InterpolationType 1 PrivateTag +(0011,"GEMS_GENIE_1",55) FD Period 1 PrivateTag +(0011,"GEMS_GENIE_1",56) FD ElapsedTime 1 PrivateTag +(0013,"GEMS_GENIE_1",10) FD DigitalFOV 2 PrivateTag +(0013,"GEMS_GENIE_1",11) SL Unknown 1 PrivateTag +(0013,"GEMS_GENIE_1",12) SL Unknown 1 PrivateTag +(0013,"GEMS_GENIE_1",16) SL AutoTrackPeak 1 PrivateTag +(0013,"GEMS_GENIE_1",17) SL AutoTrackWidth 1 PrivateTag +(0013,"GEMS_GENIE_1",18) FD TransmissionScanTime 1 PrivateTag +(0013,"GEMS_GENIE_1",19) FD TransmissionMaskWidth 1 PrivateTag +(0013,"GEMS_GENIE_1",1a) FD CopperAttenuatorThickness 1 PrivateTag +(0013,"GEMS_GENIE_1",1c) FD Unknown 1 PrivateTag +(0013,"GEMS_GENIE_1",1d) FD Unknown 1 PrivateTag +(0013,"GEMS_GENIE_1",1e) FD TomoViewOffset 1-n PrivateTag +(0013,"GEMS_GENIE_1",26) LT StudyComments 1 PrivateTag + +(0033,"GEMS_GNHD_01",01) UN Unknown 1 PrivateTag +(0033,"GEMS_GNHD_01",02) UN Unknown 1 PrivateTag + +(0009,"GEMS_IDEN_01",01) LO FullFidelity 1 PrivateTag +(0009,"GEMS_IDEN_01",02) SH SuiteId 1 PrivateTag +(0009,"GEMS_IDEN_01",04) SH ProductId 1 PrivateTag +(0009,"GEMS_IDEN_01",17) LT Unknown 1 PrivateTag +(0009,"GEMS_IDEN_01",1a) US Unknown 1 PrivateTag +(0009,"GEMS_IDEN_01",20) US Unknown 1 PrivateTag +(0009,"GEMS_IDEN_01",27) SL ImageActualDate 1 PrivateTag +(0009,"GEMS_IDEN_01",2f) LT Unknown 1 PrivateTag +(0009,"GEMS_IDEN_01",30) SH ServiceId 1 PrivateTag +(0009,"GEMS_IDEN_01",31) SH MobileLocationNumber 1 PrivateTag +(0009,"GEMS_IDEN_01",e2) LT Unknown 1 PrivateTag +(0009,"GEMS_IDEN_01",e3) UI EquipmentUID 1 PrivateTag +(0009,"GEMS_IDEN_01",e6) SH GenesisVersionNow 1 PrivateTag +(0009,"GEMS_IDEN_01",e7) UL ExamRecordChecksum 1 PrivateTag +(0009,"GEMS_IDEN_01",e8) UL Unknown 1 PrivateTag +(0009,"GEMS_IDEN_01",e9) SL ActualSeriesDataTimeStamp 1 PrivateTag + +(0027,"GEMS_IMAG_01",06) SL ImageArchiveFlag 1 PrivateTag +(0027,"GEMS_IMAG_01",10) SS ScoutType 1 PrivateTag +(0027,"GEMS_IMAG_01",1c) SL VmaMamp 1 PrivateTag +(0027,"GEMS_IMAG_01",1d) SS VmaPhase 1 PrivateTag +(0027,"GEMS_IMAG_01",1e) SL VmaMod 1 PrivateTag +(0027,"GEMS_IMAG_01",1f) SL VmaClip 1 PrivateTag +(0027,"GEMS_IMAG_01",20) SS SmartScanOnOffFlag 1 PrivateTag +(0027,"GEMS_IMAG_01",30) SH ForeignImageRevision 1 PrivateTag +(0027,"GEMS_IMAG_01",31) SS ImagingMode 1 PrivateTag +(0027,"GEMS_IMAG_01",32) SS PulseSequence 1 PrivateTag +(0027,"GEMS_IMAG_01",33) SL ImagingOptions 1 PrivateTag +(0027,"GEMS_IMAG_01",35) SS PlaneType 1 PrivateTag +(0027,"GEMS_IMAG_01",36) SL ObliquePlane 1 PrivateTag +(0027,"GEMS_IMAG_01",40) SH RASLetterOfImageLocation 1 PrivateTag +(0027,"GEMS_IMAG_01",41) FL ImageLocation 1 PrivateTag +(0027,"GEMS_IMAG_01",42) FL CenterRCoordOfPlaneImage 1 PrivateTag +(0027,"GEMS_IMAG_01",43) FL CenterACoordOfPlaneImage 1 PrivateTag +(0027,"GEMS_IMAG_01",44) FL CenterSCoordOfPlaneImage 1 PrivateTag +(0027,"GEMS_IMAG_01",45) FL NormalRCoord 1 PrivateTag +(0027,"GEMS_IMAG_01",46) FL NormalACoord 1 PrivateTag +(0027,"GEMS_IMAG_01",47) FL NormalSCoord 1 PrivateTag +(0027,"GEMS_IMAG_01",48) FL RCoordOfTopRightCorner 1 PrivateTag +(0027,"GEMS_IMAG_01",49) FL ACoordOfTopRightCorner 1 PrivateTag +(0027,"GEMS_IMAG_01",4a) FL SCoordOfTopRightCorner 1 PrivateTag +(0027,"GEMS_IMAG_01",4b) FL RCoordOfBottomRightCorner 1 PrivateTag +(0027,"GEMS_IMAG_01",4c) FL ACoordOfBottomRightCorner 1 PrivateTag +(0027,"GEMS_IMAG_01",4d) FL SCoordOfBottomRightCorner 1 PrivateTag +(0027,"GEMS_IMAG_01",50) FL TableStartLocation 1 PrivateTag +(0027,"GEMS_IMAG_01",51) FL TableEndLocation 1 PrivateTag +(0027,"GEMS_IMAG_01",52) SH RASLetterForSideOfImage 1 PrivateTag +(0027,"GEMS_IMAG_01",53) SH RASLetterForAnteriorPosterior 1 PrivateTag +(0027,"GEMS_IMAG_01",54) SH RASLetterForScoutStartLoc 1 PrivateTag +(0027,"GEMS_IMAG_01",55) SH RASLetterForScoutEndLoc 1 PrivateTag +(0027,"GEMS_IMAG_01",60) FL ImageDimensionX 1 PrivateTag +(0027,"GEMS_IMAG_01",61) FL ImageDimensionY 1 PrivateTag +(0027,"GEMS_IMAG_01",62) FL NumberOfExcitations 1 PrivateTag + +(0029,"GEMS_IMPS_01",04) SL LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",05) DS LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",06) DS LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",07) SL LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",08) SH LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",09) SH LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",0a) SS LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",15) SL LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",16) SL LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",17) SL LowerRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",18) SL UpperRangeOfPixels 1 PrivateTag +(0029,"GEMS_IMPS_01",1a) SL LengthOfTotalHeaderInBytes 1 PrivateTag +(0029,"GEMS_IMPS_01",26) SS VersionOfHeaderStructure 1 PrivateTag +(0029,"GEMS_IMPS_01",34) SL AdvantageCompOverflow 1 PrivateTag +(0029,"GEMS_IMPS_01",35) SL AdvantageCompUnderflow 1 PrivateTag + +(0043,"GEMS_PARM_01",01) SS BitmapOfPrescanOptions 1 PrivateTag +(0043,"GEMS_PARM_01",02) SS GradientOffsetInX 1 PrivateTag +(0043,"GEMS_PARM_01",03) SS GradientOffsetInY 1 PrivateTag +(0043,"GEMS_PARM_01",04) SS GradientOffsetInZ 1 PrivateTag +(0043,"GEMS_PARM_01",05) SS ImageIsOriginalOrUnoriginal 1 PrivateTag +(0043,"GEMS_PARM_01",06) SS NumberOfEPIShots 1 PrivateTag +(0043,"GEMS_PARM_01",07) SS ViewsPerSegment 1 PrivateTag +(0043,"GEMS_PARM_01",08) SS RespiratoryRateInBPM 1 PrivateTag +(0043,"GEMS_PARM_01",09) SS RespiratoryTriggerPoint 1 PrivateTag +(0043,"GEMS_PARM_01",0a) SS TypeOfReceiverUsed 1 PrivateTag +(0043,"GEMS_PARM_01",0b) DS PeakRateOfChangeOfGradientField 1 PrivateTag +(0043,"GEMS_PARM_01",0c) DS LimitsInUnitsOfPercent 1 PrivateTag +(0043,"GEMS_PARM_01",0d) DS PSDEstimatedLimit 1 PrivateTag +(0043,"GEMS_PARM_01",0e) DS PSDEstimatedLimitInTeslaPerSecond 1 PrivateTag +(0043,"GEMS_PARM_01",0f) DS SARAvgHead 1 PrivateTag +(0043,"GEMS_PARM_01",10) US WindowValue 1 PrivateTag +(0043,"GEMS_PARM_01",11) US TotalInputViews 1 PrivateTag +(0043,"GEMS_PARM_01",12) SS XrayChain 3 PrivateTag +(0043,"GEMS_PARM_01",13) SS ReconKernelParameters 5 PrivateTag +(0043,"GEMS_PARM_01",14) SS CalibrationParameters 3 PrivateTag +(0043,"GEMS_PARM_01",15) SS TotalOutputViews 3 PrivateTag +(0043,"GEMS_PARM_01",16) SS NumberOfOverranges 5 PrivateTag +(0043,"GEMS_PARM_01",17) DS IBHImageScaleFactors 1 PrivateTag +(0043,"GEMS_PARM_01",18) DS BBHCoefficients 3 PrivateTag +(0043,"GEMS_PARM_01",19) SS NumberOfBBHChainsToBlend 1 PrivateTag +(0043,"GEMS_PARM_01",1a) SL StartingChannelNumber 1 PrivateTag +(0043,"GEMS_PARM_01",1b) SS PPScanParameters 1 PrivateTag +(0043,"GEMS_PARM_01",1c) SS GEImageIntegrity 1 PrivateTag +(0043,"GEMS_PARM_01",1d) SS LevelValue 1 PrivateTag +(0043,"GEMS_PARM_01",1e) DS DeltaStartTime 1 PrivateTag +(0043,"GEMS_PARM_01",1f) SL MaxOverrangesInAView 1 PrivateTag +(0043,"GEMS_PARM_01",20) DS AvgOverrangesAllViews 1 PrivateTag +(0043,"GEMS_PARM_01",21) SS CorrectedAfterglowTerms 1 PrivateTag +(0043,"GEMS_PARM_01",25) SS ReferenceChannels 6 PrivateTag +(0043,"GEMS_PARM_01",26) US NoViewsRefChannelsBlocked 6 PrivateTag +(0043,"GEMS_PARM_01",27) SH ScanPitchRatio 1 PrivateTag +(0043,"GEMS_PARM_01",28) OB UniqueImageIdentifier 1 PrivateTag +(0043,"GEMS_PARM_01",29) OB HistogramTables 1 PrivateTag +(0043,"GEMS_PARM_01",2a) OB UserDefinedData 1 PrivateTag +(0043,"GEMS_PARM_01",2b) SS PrivateScanOptions 4 PrivateTag +(0043,"GEMS_PARM_01",2c) SS EffectiveEchoSpacing 1 PrivateTag +(0043,"GEMS_PARM_01",2d) SH StringSlopField1 1 PrivateTag +(0043,"GEMS_PARM_01",2e) SH StringSlopField2 1 PrivateTag +(0043,"GEMS_PARM_01",2f) SS RawDataType 1 PrivateTag +(0043,"GEMS_PARM_01",30) SS RawDataType 1 PrivateTag +(0043,"GEMS_PARM_01",31) DS RACoordOfTargetReconCentre 2 PrivateTag +(0043,"GEMS_PARM_01",32) SS RawDataType 1 PrivateTag +(0043,"GEMS_PARM_01",33) FL NegScanSpacing 1 PrivateTag +(0043,"GEMS_PARM_01",34) IS OffsetFrequency 1 PrivateTag +(0043,"GEMS_PARM_01",35) UL UserUsageTag 1 PrivateTag +(0043,"GEMS_PARM_01",36) UL UserFillMapMSW 1 PrivateTag +(0043,"GEMS_PARM_01",37) UL UserFillMapLSW 1 PrivateTag +(0043,"GEMS_PARM_01",38) FL User25ToUser48 24 PrivateTag +(0043,"GEMS_PARM_01",39) IS SlopInteger6ToSlopInteger9 4 PrivateTag +(0043,"GEMS_PARM_01",40) FL TriggerOnPosition 4 PrivateTag +(0043,"GEMS_PARM_01",41) FL DegreeOfRotation 4 PrivateTag +(0043,"GEMS_PARM_01",42) SL DASTriggerSource 4 PrivateTag +(0043,"GEMS_PARM_01",43) SL DASFpaGain 4 PrivateTag +(0043,"GEMS_PARM_01",44) SL DASOutputSource 4 PrivateTag +(0043,"GEMS_PARM_01",45) SL DASAdInput 4 PrivateTag +(0043,"GEMS_PARM_01",46) SL DASCalMode 4 PrivateTag +(0043,"GEMS_PARM_01",47) SL DASCalFrequency 4 PrivateTag +(0043,"GEMS_PARM_01",48) SL DASRegXm 4 PrivateTag +(0043,"GEMS_PARM_01",49) SL DASAutoZero 4 PrivateTag +(0043,"GEMS_PARM_01",4a) SS StartingChannelOfView 4 PrivateTag +(0043,"GEMS_PARM_01",4b) SL DASXmPattern 4 PrivateTag +(0043,"GEMS_PARM_01",4c) SS TGGCTriggerMode 4 PrivateTag +(0043,"GEMS_PARM_01",4d) FL StartScanToXrayOnDelay 4 PrivateTag +(0043,"GEMS_PARM_01",4e) FL DurationOfXrayOn 4 PrivateTag +(0043,"GEMS_PARM_01",60) IS SlopInteger10ToSlopInteger17 8 PrivateTag +(0043,"GEMS_PARM_01",61) UI ScannerStudyEntityUID 1 PrivateTag +(0043,"GEMS_PARM_01",62) SH ScannerStudyID 1 PrivateTag +(0043,"GEMS_PARM_01",6f) DS ScannerTableEntry 3 PrivateTag +(0043,"GEMS_PARM_01",70) LO ParadigmName 1 PrivateTag +(0043,"GEMS_PARM_01",71) ST ParadigmDescription 1 PrivateTag +(0043,"GEMS_PARM_01",72) UI ParadigmUID 1 PrivateTag +(0043,"GEMS_PARM_01",73) US ExperimentType 1 PrivateTag +(0043,"GEMS_PARM_01",74) US NumberOfRestVolumes 1 PrivateTag +(0043,"GEMS_PARM_01",75) US NumberOfActiveVolumes 1 PrivateTag +(0043,"GEMS_PARM_01",76) US NumberOfDummyScans 1 PrivateTag +(0043,"GEMS_PARM_01",77) SH ApplicationName 1 PrivateTag +(0043,"GEMS_PARM_01",78) SH ApplicationVersion 1 PrivateTag +(0043,"GEMS_PARM_01",79) US SlicesPerVolume 1 PrivateTag +(0043,"GEMS_PARM_01",7a) US ExpectedTimePoints 1 PrivateTag +(0043,"GEMS_PARM_01",7b) FL RegressorValues 1-n PrivateTag +(0043,"GEMS_PARM_01",7c) FL DelayAfterSliceGroup 1 PrivateTag +(0043,"GEMS_PARM_01",7d) US ReconModeFlagWord 1 PrivateTag +(0043,"GEMS_PARM_01",7e) LO PACCSpecificInformation 1-n PrivateTag +(0043,"GEMS_PARM_01",7f) DS EDWIScaleFactor 1-n PrivateTag +(0043,"GEMS_PARM_01",80) LO CoilIDData 1-n PrivateTag +(0043,"GEMS_PARM_01",81) LO GECoilName 1 PrivateTag +(0043,"GEMS_PARM_01",82) LO SystemConfigurationInformation 1-n PrivateTag +(0043,"GEMS_PARM_01",83) DS AssetRFactors 1-2 PrivateTag +(0043,"GEMS_PARM_01",84) LO AdditionalAssetData 5-n PrivateTag +(0043,"GEMS_PARM_01",85) UT DebugDataTextFormat 1 PrivateTag +(0043,"GEMS_PARM_01",86) OB DebugDataBinaryFormat 1 PrivateTag +(0043,"GEMS_PARM_01",87) UT ScannerSoftwareVersionLongForm 1 PrivateTag +(0043,"GEMS_PARM_01",88) UI PUREAcquisitionCalibrationSeriesUID 1 PrivateTag +(0043,"GEMS_PARM_01",89) LO GoverningBodydBdtAndSARDefinition 3 PrivateTag +(0043,"GEMS_PARM_01",8a) CS PrivateInPlanePhaseEncodingDirection 1 PrivateTag +(0043,"GEMS_PARM_01",8b) OB FMRIBinaryDataBlock 1 PrivateTag +(0043,"GEMS_PARM_01",8c) DS VoxelLocation 6 PrivateTag +(0043,"GEMS_PARM_01",8d) DS SATBandLocations 7-7n PrivateTag +(0043,"GEMS_PARM_01",8e) DS SpectroPrescanValues 3 PrivateTag +(0043,"GEMS_PARM_01",8f) DS SpectroParameters 3 PrivateTag +(0043,"GEMS_PARM_01",90) LO SARDefinition 1-n PrivateTag +(0043,"GEMS_PARM_01",91) DS SARValue 1-n PrivateTag +(0043,"GEMS_PARM_01",92) LO ImageErrorText 1 PrivateTag +(0043,"GEMS_PARM_01",93) DS SpectroQuantitationValues 1-n PrivateTag +(0043,"GEMS_PARM_01",94) DS SpectroRatioValues 1-n PrivateTag +(0043,"GEMS_PARM_01",95) LO PrescanReuseString 1 PrivateTag +(0043,"GEMS_PARM_01",96) CS ContentQualification 1 PrivateTag +(0043,"GEMS_PARM_01",97) LO ImageFilteringParameters 9 PrivateTag +(0043,"GEMS_PARM_01",98) UI ASSETAcquisitionCalibrationSeriesUID 1 PrivateTag +(0043,"GEMS_PARM_01",99) LO ExtendedOptions 1-n PrivateTag +(0043,"GEMS_PARM_01",9a) IS RxStackIdentification 1 PrivateTag +(0043,"GEMS_PARM_01",9b) DS NPWFactor 1 PrivateTag +(0043,"GEMS_PARM_01",9c) OB ResearchTag1 1 PrivateTag +(0043,"GEMS_PARM_01",9d) OB ResearchTag2 1 PrivateTag +(0043,"GEMS_PARM_01",9e) OB ResearchTag3 1 PrivateTag +(0043,"GEMS_PARM_01",9f) OB ResearchTag4 1 PrivateTag + +(0011,"GEMS_PATI_01",10) SS PatientStatus 1 PrivateTag + +(0021,"GEMS_RELA_01",03) SS SeriesFromWhichPrescribed 1 PrivateTag +(0021,"GEMS_RELA_01",05) SH GenesisVersionNow 1 PrivateTag +(0021,"GEMS_RELA_01",07) UL SeriesRecordChecksum 1 PrivateTag +(0021,"GEMS_RELA_01",15) US Unknown 1 PrivateTag +(0021,"GEMS_RELA_01",16) SS Unknown 1 PrivateTag +(0021,"GEMS_RELA_01",18) SH GenesisVersionNow 1 PrivateTag +(0021,"GEMS_RELA_01",19) UL AcqReconRecordChecksum 1 PrivateTag +(0021,"GEMS_RELA_01",20) DS TableStartLocation 1 PrivateTag +(0021,"GEMS_RELA_01",35) SS SeriesFromWhichPrescribed 1 PrivateTag +(0021,"GEMS_RELA_01",36) SS ImageFromWhichPrescribed 1 PrivateTag +(0021,"GEMS_RELA_01",37) SS ScreenFormat 1 PrivateTag +(0021,"GEMS_RELA_01",4a) LO AnatomicalReferenceForScout 1 PrivateTag +(0021,"GEMS_RELA_01",4e) US Unknown 1 PrivateTag +(0021,"GEMS_RELA_01",4f) SS LocationsInAcquisition 1 PrivateTag +(0021,"GEMS_RELA_01",50) SS GraphicallyPrescribed 1 PrivateTag +(0021,"GEMS_RELA_01",51) DS RotationFromSourceXRot 1 PrivateTag +(0021,"GEMS_RELA_01",52) DS RotationFromSourceYRot 1 PrivateTag +(0021,"GEMS_RELA_01",53) DS RotationFromSourceZRot 1 PrivateTag +(0021,"GEMS_RELA_01",54) SH ImagePosition 3 PrivateTag +(0021,"GEMS_RELA_01",55) SH ImageOrientation 6 PrivateTag +(0021,"GEMS_RELA_01",56) SL IntegerSlop 1 PrivateTag +(0021,"GEMS_RELA_01",57) SL IntegerSlop 1 PrivateTag +(0021,"GEMS_RELA_01",58) SL IntegerSlop 1 PrivateTag +(0021,"GEMS_RELA_01",59) SL IntegerSlop 1 PrivateTag +(0021,"GEMS_RELA_01",5a) SL IntegerSlop 1 PrivateTag +(0021,"GEMS_RELA_01",5b) DS FloatSlop 1 PrivateTag +(0021,"GEMS_RELA_01",5c) DS FloatSlop 1 PrivateTag +(0021,"GEMS_RELA_01",5d) DS FloatSlop 1 PrivateTag +(0021,"GEMS_RELA_01",5e) DS FloatSlop 1 PrivateTag +(0021,"GEMS_RELA_01",5f) DS FloatSlop 1 PrivateTag +(0021,"GEMS_RELA_01",70) LT Unknown 1 PrivateTag +(0021,"GEMS_RELA_01",71) LT Unknown 1 PrivateTag +(0021,"GEMS_RELA_01",81) DS AutoWindowLevelAlpha 1 PrivateTag +(0021,"GEMS_RELA_01",82) DS AutoWindowLevelBeta 1 PrivateTag +(0021,"GEMS_RELA_01",83) DS AutoWindowLevelWindow 1 PrivateTag +(0021,"GEMS_RELA_01",84) DS AutoWindowLevelLevel 1 PrivateTag +(0021,"GEMS_RELA_01",90) SS TubeFocalSpotPosition 1 PrivateTag +(0021,"GEMS_RELA_01",91) SS BiopsyPosition 1 PrivateTag +(0021,"GEMS_RELA_01",92) FL BiopsyTLocation 1 PrivateTag +(0021,"GEMS_RELA_01",93) FL BiopsyRefLocation 1 PrivateTag + +(0045,"GEMS_SENO_02",04) CS AES 1 PrivateTag +(0045,"GEMS_SENO_02",06) DS Angulation 1 PrivateTag +(0045,"GEMS_SENO_02",09) DS RealMagnificationFactor 1 PrivateTag +(0045,"GEMS_SENO_02",0b) CS SenographType 1 PrivateTag +(0045,"GEMS_SENO_02",0c) DS IntegrationTime 1 PrivateTag +(0045,"GEMS_SENO_02",0d) DS ROIOriginXY 1 PrivateTag +(0045,"GEMS_SENO_02",11) DS ReceptorSizeCmXY 2 PrivateTag +(0045,"GEMS_SENO_02",12) IS ReceptorSizePixelsXY 2 PrivateTag +(0045,"GEMS_SENO_02",13) ST Screen 1 PrivateTag +(0045,"GEMS_SENO_02",14) DS PixelPitchMicrons 1 PrivateTag +(0045,"GEMS_SENO_02",15) IS PixelDepthBits 1 PrivateTag +(0045,"GEMS_SENO_02",16) IS BinningFactorXY 2 PrivateTag +(0045,"GEMS_SENO_02",1B) CS ClinicalView 1 PrivateTag +(0045,"GEMS_SENO_02",1D) DS MeanOfRawGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",1E) DS MeanOfOffsetGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",1F) DS MeanOfCorrectedGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",20) DS MeanOfRegionGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",21) DS MeanOfLogRegionGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",22) DS StandardDeviationOfRawGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",23) DS StandardDeviationOfCorrectedGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",24) DS StandardDeviationOfRegionGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",25) DS StandardDeviationOfLogRegionGrayLevels 1 PrivateTag +(0045,"GEMS_SENO_02",26) OB MAOBuffer 1 PrivateTag +(0045,"GEMS_SENO_02",27) IS SetNumber 1 PrivateTag +(0045,"GEMS_SENO_02",28) CS WindowingType 1 PrivateTag +(0045,"GEMS_SENO_02",29) DS WindowingParameters 1-n PrivateTag +(0045,"GEMS_SENO_02",2a) IS CrosshairCursorXCoordinates 1 PrivateTag +(0045,"GEMS_SENO_02",2b) IS CrosshairCursorYCoordinates 1 PrivateTag +(0045,"GEMS_SENO_02",39) US VignetteRows 1 PrivateTag +(0045,"GEMS_SENO_02",3a) US VignetteColumns 1 PrivateTag +(0045,"GEMS_SENO_02",3b) US VignetteBitsAllocated 1 PrivateTag +(0045,"GEMS_SENO_02",3c) US VignetteBitsStored 1 PrivateTag +(0045,"GEMS_SENO_02",3d) US VignetteHighBit 1 PrivateTag +(0045,"GEMS_SENO_02",3e) US VignettePixelRepresentation 1 PrivateTag +(0045,"GEMS_SENO_02",3f) OB VignettePixelData 1 PrivateTag + +(0025,"GEMS_SERS_01",06) SS LastPulseSequenceUsed 1 PrivateTag +(0025,"GEMS_SERS_01",07) SL ImagesInSeries 1 PrivateTag +(0025,"GEMS_SERS_01",10) SL LandmarkCounter 1 PrivateTag +(0025,"GEMS_SERS_01",11) SS NumberOfAcquisitions 1 PrivateTag +(0025,"GEMS_SERS_01",14) SL IndicatesNumberOfUpdatesToHeader 1 PrivateTag +(0025,"GEMS_SERS_01",17) SL SeriesCompleteFlag 1 PrivateTag +(0025,"GEMS_SERS_01",18) SL NumberOfImagesArchived 1 PrivateTag +(0025,"GEMS_SERS_01",19) SL LastImageNumberUsed 1 PrivateTag +(0025,"GEMS_SERS_01",1a) SH PrimaryReceiverSuiteAndHost 1 PrivateTag + +(0023,"GEMS_STDY_01",01) SL NumberOfSeriesInStudy 1 PrivateTag +(0023,"GEMS_STDY_01",02) SL NumberOfUnarchivedSeries 1 PrivateTag +(0023,"GEMS_STDY_01",10) SS ReferenceImageField 1 PrivateTag +(0023,"GEMS_STDY_01",50) SS SummaryImage 1 PrivateTag +(0023,"GEMS_STDY_01",70) FD StartTimeSecsInFirstAxial 1 PrivateTag +(0023,"GEMS_STDY_01",74) SL NumberOfUpdatesToHeader 1 PrivateTag +(0023,"GEMS_STDY_01",7d) SS IndicatesIfStudyHasCompleteInfo 1 PrivateTag + +(0033,"GEMS_YMHD_01",05) UN Unknown 1 PrivateTag +(0033,"GEMS_YMHD_01",06) UN Unknown 1 PrivateTag + +(0019,"GE_GENESIS_REV3.0",39) SS AxialType 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",8f) SS SwapPhaseFrequency 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",9c) SS PulseSequenceName 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",9f) SS CoilType 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",a4) SS SATFatWaterBone 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",c0) SS BitmapOfSATSelections 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",c1) SS SurfaceCoilIntensityCorrectionFlag 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",cb) SS PhaseContrastFlowAxis 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",cc) SS PhaseContrastVelocityEncoding 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",d5) SS FractionalEcho 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",d8) SS VariableEchoFlag 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",d9) DS ConcatenatedSat 1 PrivateTag +(0019,"GE_GENESIS_REV3.0",f2) SS NumberOfPhases 1 PrivateTag +(0043,"GE_GENESIS_REV3.0",1e) DS DeltaStartTime 1 PrivateTag +(0043,"GE_GENESIS_REV3.0",27) SH ScanPitchRatio 1 PrivateTag + +(0029,"INTELERAD MEDICAL SYSTEMS",01) FD ImageCompressionFraction 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",02) FD ImageQuality 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",03) FD ImageBytesTransferred 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",10) SH J2cParameterType 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",11) US J2cPixelRepresentation 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",12) US J2cBitsAllocated 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",13) US J2cPixelShiftValue 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",14) US J2cPlanarConfiguration 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",15) DS J2cRescaleIntercept 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",20) LO PixelDataMD5SumPerFrame 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",21) US HistogramPercentileLabels 1 PrivateTag +(0029,"INTELERAD MEDICAL SYSTEMS",22) FD HistogramPercentileValues 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",01) LO InstitutionCode 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",02) LO RoutedTransferAE 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",03) LO SourceAE 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",04) SH DeferredValidation 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",05) LO SeriesOwner 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",06) LO OrderGroupNumber 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",07) SH StrippedPixelData 1 PrivateTag +(3f01,"INTELERAD MEDICAL SYSTEMS",08) SH PendingMoveRequest 1 PrivateTag + +(0041,"INTEGRIS 1.0",20) FL AccumulatedFluoroscopyDose 1 PrivateTag +(0041,"INTEGRIS 1.0",30) FL AccumulatedExposureDose 1 PrivateTag +(0041,"INTEGRIS 1.0",40) FL TotalDose 1 PrivateTag +(0041,"INTEGRIS 1.0",41) FL TotalNumberOfFrames 1 PrivateTag +(0041,"INTEGRIS 1.0",50) SQ ExposureInformationSequence 1 PrivateTag +(0009,"INTEGRIS 1.0",08) CS ExposureChannel 1-n PrivateTag +(0009,"INTEGRIS 1.0",32) TM ExposureStartTime 1 PrivateTag +(0019,"INTEGRIS 1.0",00) LO APRName 1 PrivateTag +(0019,"INTEGRIS 1.0",40) DS FrameRate 1 PrivateTag +(0021,"INTEGRIS 1.0",12) IS ExposureNumber 1 PrivateTag +(0029,"INTEGRIS 1.0",08) IS NumberOfExposureResults 1 PrivateTag + +(0029,"ISG shadow",70) IS Unknown 1 PrivateTag +(0029,"ISG shadow",80) IS Unknown 1 PrivateTag +(0029,"ISG shadow",90) IS Unknown 1 PrivateTag + +(0009,"ISI",01) UN SIENETGeneralPurposeIMGEF 1 PrivateTag + +(0009,"MERGE TECHNOLOGIES, INC.",00) OB Unknown 1 PrivateTag + +(0029,"OCULUS Optikgeraete GmbH",1010) OB OriginalMeasuringData 1 PrivateTag +(0029,"OCULUS Optikgeraete GmbH",1012) UL OriginalMeasuringDataLength 1 PrivateTag +(0029,"OCULUS Optikgeraete GmbH",1020) OB OriginalMeasuringRawData 1 PrivateTag +(0029,"OCULUS Optikgeraete GmbH",1022) UL OriginalMeasuringRawDataLength 1 PrivateTag + +(0041,"PAPYRUS 3.0",00) LT PapyrusComments 1 PrivateTag +(0041,"PAPYRUS 3.0",10) SQ PointerSequence 1 PrivateTag +(0041,"PAPYRUS 3.0",11) UL ImagePointer 1 PrivateTag +(0041,"PAPYRUS 3.0",12) UL PixelOffset 1 PrivateTag +(0041,"PAPYRUS 3.0",13) SQ ImageIdentifierSequence 1 PrivateTag +(0041,"PAPYRUS 3.0",14) SQ ExternalFileReferenceSequence 1 PrivateTag +(0041,"PAPYRUS 3.0",15) US NumberOfImages 1 PrivateTag +(0041,"PAPYRUS 3.0",21) UI ReferencedSOPClassUID 1 PrivateTag +(0041,"PAPYRUS 3.0",22) UI ReferencedSOPInstanceUID 1 PrivateTag +(0041,"PAPYRUS 3.0",31) LT ReferencedFileName 1 PrivateTag +(0041,"PAPYRUS 3.0",32) LT ReferencedFilePath 1-n PrivateTag +(0041,"PAPYRUS 3.0",41) UI ReferencedImageSOPClassUID 1 PrivateTag +(0041,"PAPYRUS 3.0",42) UI ReferencedImageSOPInstanceUID 1 PrivateTag +(0041,"PAPYRUS 3.0",50) SQ ImageSequence 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",00) IS OverlayID 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",01) LT LinkedOverlays 1-n PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",10) US OverlayRows 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",11) US OverlayColumns 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",40) LO OverlayType 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",50) US OverlayOrigin 1-n PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",60) LO Editable 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",70) LO OverlayFont 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",72) LO OverlayStyle 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",74) US OverlayFontSize 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",76) LO OverlayColor 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",78) US ShadowSize 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",80) LO FillPattern 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",82) US OverlayPenSize 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",a0) LO Label 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",a2) LT PostItText 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",a4) US AnchorPoint 2 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",b0) LO ROIType 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",b2) LT AttachedAnnotation 1 PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",ba) US ContourPoints 1-n PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",bc) US MaskData 1-n PrivateTag +(6001-o-60ff,"PAPYRUS 3.0",c0) SQ UINOverlaySequence 1 PrivateTag + +(0009,"PAPYRUS",00) LT OriginalFileName 1 PrivateTag +(0009,"PAPYRUS",10) LT OriginalFileLocation 1 PrivateTag +(0009,"PAPYRUS",18) LT DataSetIdentifier 1 PrivateTag +(0041,"PAPYRUS",00) LT PapyrusComments 1-n PrivateTag +(0041,"PAPYRUS",10) US FolderType 1 PrivateTag +(0041,"PAPYRUS",11) LT PatientFolderDataSetID 1 PrivateTag +(0041,"PAPYRUS",20) LT FolderName 1 PrivateTag +(0041,"PAPYRUS",30) DA CreationDate 1 PrivateTag +(0041,"PAPYRUS",32) TM CreationTime 1 PrivateTag +(0041,"PAPYRUS",34) DA ModifiedDate 1 PrivateTag +(0041,"PAPYRUS",36) TM ModifiedTime 1 PrivateTag +(0041,"PAPYRUS",40) LT OwnerName 1-n PrivateTag +(0041,"PAPYRUS",50) LT FolderStatus 1 PrivateTag +(0041,"PAPYRUS",60) UL NumberOfImages 1 PrivateTag +(0041,"PAPYRUS",62) UL NumberOfOther 1 PrivateTag +(0041,"PAPYRUS",a0) LT ExternalFolderElementDSID 1-n PrivateTag +(0041,"PAPYRUS",a1) US ExternalFolderElementDataSetType 1-n PrivateTag +(0041,"PAPYRUS",a2) LT ExternalFolderElementFileLocation 1-n PrivateTag +(0041,"PAPYRUS",a3) UL ExternalFolderElementLength 1-n PrivateTag +(0041,"PAPYRUS",b0) LT InternalFolderElementDSID 1-n PrivateTag +(0041,"PAPYRUS",b1) US InternalFolderElementDataSetType 1-n PrivateTag +(0041,"PAPYRUS",b2) UL InternalOffsetToDataSet 1-n PrivateTag +(0041,"PAPYRUS",b3) UL InternalOffsetToImage 1-n + +# Note: Some Philips devices use these private tags with reservation value +# "Philips Imaging DD 001", others use "PHILIPS IMAGING DD 001". All attributes +# should thus be present twice in this dictionary, once for each spelling variant. +# +(2001,"Philips Imaging DD 001",01) FL ChemicalShift 1 PrivateTag +(2001,"Philips Imaging DD 001",02) IS ChemicalShiftNumberMR 1 PrivateTag +(2001,"Philips Imaging DD 001",03) FL DiffusionBFactor 1 PrivateTag +(2001,"Philips Imaging DD 001",04) CS DiffusionDirection 1 PrivateTag +(2001,"Philips Imaging DD 001",06) CS ImageEnhanced 1 PrivateTag +(2001,"Philips Imaging DD 001",07) CS ImageTypeEDES 1 PrivateTag +(2001,"Philips Imaging DD 001",08) IS PhaseNumber 1 PrivateTag +(2001,"Philips Imaging DD 001",09) FL ImagePrepulseDelay 1 PrivateTag +(2001,"Philips Imaging DD 001",0a) IS SliceNumberMR 1 PrivateTag +(2001,"Philips Imaging DD 001",0b) CS SliceOrientation 1 PrivateTag +(2001,"Philips Imaging DD 001",0c) CS ArrhythmiaRejection 1 PrivateTag +(2001,"Philips Imaging DD 001",0e) CS CardiacCycled 1 PrivateTag +(2001,"Philips Imaging DD 001",0f) SS CardiacGateWidth 1 PrivateTag +(2001,"Philips Imaging DD 001",10) CS CardiacSync 1 PrivateTag +(2001,"Philips Imaging DD 001",11) FL DiffusionEchoTime 1 PrivateTag +(2001,"Philips Imaging DD 001",12) CS DynamicSeries 1 PrivateTag +(2001,"Philips Imaging DD 001",13) SL EPIFactor 1 PrivateTag +(2001,"Philips Imaging DD 001",14) SL NumberOfEchoes 1 PrivateTag +(2001,"Philips Imaging DD 001",15) SS NumberOfLocations 1 PrivateTag +(2001,"Philips Imaging DD 001",16) SS NumberOfPCDirections 1 PrivateTag +(2001,"Philips Imaging DD 001",17) SL NumberOfPhasesMR 1 PrivateTag +(2001,"Philips Imaging DD 001",18) SL NumberOfSlicesMR 1 PrivateTag +(2001,"Philips Imaging DD 001",19) CS PartialMatrixScanned 1 PrivateTag +(2001,"Philips Imaging DD 001",1a) FL PCVelocity 1-n PrivateTag +(2001,"Philips Imaging DD 001",1b) FL PrepulseDelay 1 PrivateTag +(2001,"Philips Imaging DD 001",1c) CS PrepulseType 1 PrivateTag +(2001,"Philips Imaging DD 001",1d) IS ReconstructionNumberMR 1 PrivateTag +(2001,"Philips Imaging DD 001",1f) CS RespirationSync 1 PrivateTag +(2001,"Philips Imaging DD 001",20) LO ScanningTechnique 1 PrivateTag +(2001,"Philips Imaging DD 001",21) CS SPIR 1 PrivateTag +(2001,"Philips Imaging DD 001",22) FL WaterFatShift 1 PrivateTag +(2001,"Philips Imaging DD 001",23) DS FlipAnglePhilips 1 PrivateTag +(2001,"Philips Imaging DD 001",24) CS SeriesIsInteractive 1 PrivateTag +(2001,"Philips Imaging DD 001",25) SH EchoTimeDisplayMR 1 PrivateTag +(2001,"Philips Imaging DD 001",26) CS PresentationStateSubtractionActive 1 PrivateTag +(2001,"Philips Imaging DD 001",2d) SS StackNumberOfSlices 1 PrivateTag +(2001,"Philips Imaging DD 001",32) FL StackRadialAngle 1 PrivateTag +(2001,"Philips Imaging DD 001",33) CS StackRadialAxis 1 PrivateTag +(2001,"Philips Imaging DD 001",35) SS StackSliceNumber 1 PrivateTag +(2001,"Philips Imaging DD 001",36) CS StackType 1 PrivateTag +(2001,"Philips Imaging DD 001",3f) CS ZoomMode 1 PrivateTag +(2001,"Philips Imaging DD 001",58) UL ContrastTransferTaste 1 PrivateTag +(2001,"Philips Imaging DD 001",5f) SQ StackSequence 1 PrivateTag +(2001,"Philips Imaging DD 001",60) SL NumberOfStacks 1 PrivateTag +(2001,"Philips Imaging DD 001",61) CS SeriesTransmitted 1 PrivateTag +(2001,"Philips Imaging DD 001",62) CS SeriesCommitted 1 PrivateTag +(2001,"Philips Imaging DD 001",63) CS ExaminationSource 1 PrivateTag +(2001,"Philips Imaging DD 001",67) CS LinearPresentationGLTrafoShapeSub 1 PrivateTag +(2001,"Philips Imaging DD 001",77) CS GLTrafoType 1 PrivateTag +(2001,"Philips Imaging DD 001",7b) IS AcquisitionNumber 1 PrivateTag +(2001,"Philips Imaging DD 001",81) IS NumberOfDynamicScans 1 PrivateTag +(2001,"Philips Imaging DD 001",9f) US PixelProcessingKernelSize 1 PrivateTag +(2001,"Philips Imaging DD 001",a1) CS IsRawImage 1 PrivateTag +(2001,"Philips Imaging DD 001",f1) FL ProspectiveMotionCorrection 1 PrivateTag +(2001,"Philips Imaging DD 001",f2) FL RetrospectiveMotionCorrection 1 PrivateTag + +# Note: Some Philips devices use these private tags with reservation value +# "Philips Imaging DD 001", others use "PHILIPS IMAGING DD 001". All attributes +# should thus be present twice in this dictionary, once for each spelling variant. +# +(2001,"PHILIPS IMAGING DD 001",01) FL ChemicalShift 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",02) IS ChemicalShiftNumberMR 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",03) FL DiffusionBFactor 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",04) CS DiffusionDirection 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",06) CS ImageEnhanced 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",07) CS ImageTypeEDES 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",08) IS PhaseNumber 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",09) FL ImagePrepulseDelay 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",0a) IS SliceNumberMR 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",0b) CS SliceOrientation 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",0c) CS ArrhythmiaRejection 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",0e) CS CardiacCycled 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",0f) SS CardiacGateWidth 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",10) CS CardiacSync 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",11) FL DiffusionEchoTime 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",12) CS DynamicSeries 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",13) SL EPIFactor 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",14) SL NumberOfEchoes 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",15) SS NumberOfLocations 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",16) SS NumberOfPCDirections 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",17) SL NumberOfPhasesMR 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",18) SL NumberOfSlicesMR 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",19) CS PartialMatrixScanned 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",1a) FL PCVelocity 1-n PrivateTag +(2001,"PHILIPS IMAGING DD 001",1b) FL PrepulseDelay 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",1c) CS PrepulseType 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",1d) IS ReconstructionNumberMR 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",1f) CS RespirationSync 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",20) LO ScanningTechnique 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",21) CS SPIR 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",22) FL WaterFatShift 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",23) DS FlipAnglePhilips 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",24) CS SeriesIsInteractive 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",25) SH EchoTimeDisplayMR 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",26) CS PresentationStateSubtractionActive 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",2d) SS StackNumberOfSlices 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",32) FL StackRadialAngle 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",33) CS StackRadialAxis 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",35) SS StackSliceNumber 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",36) CS StackType 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",3f) CS ZoomMode 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",58) UL ContrastTransferTaste 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",5f) SQ StackSequence 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",60) SL NumberOfStacks 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",61) CS SeriesTransmitted 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",62) CS SeriesCommitted 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",63) CS ExaminationSource 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",67) CS LinearPresentationGLTrafoShapeSub 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",77) CS GLTrafoType 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",7b) IS AcquisitionNumber 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",81) IS NumberOfDynamicScans 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",9f) US PixelProcessingKernelSize 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",a1) CS IsRawImage 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",f1) FL ProspectiveMotionCorrection 1 PrivateTag +(2001,"PHILIPS IMAGING DD 001",f2) FL RetrospectiveMotionCorrection 1 PrivateTag + +# Note: Some Philips devices use these private tags with reservation value +# "Philips MR Imaging DD 001", others use "PHILIPS MR IMAGING DD 001". All attributes +# should thus be present twice in this dictionary, once for each spelling variant. +# +(2005,"Philips MR Imaging DD 001",05) CS SynergyReconstructionType 1 PrivateTag +(2005,"Philips MR Imaging DD 001",1e) SH MIPProtocol 1 PrivateTag +(2005,"Philips MR Imaging DD 001",1f) SH MPRProtocol 1 PrivateTag +(2005,"Philips MR Imaging DD 001",20) SL NumberOfChemicalShifts 1 PrivateTag +(2005,"Philips MR Imaging DD 001",2d) SS NumberOfStackSlices 1 PrivateTag +(2005,"Philips MR Imaging DD 001",83) SQ Unknown 1 PrivateTag +(2005,"Philips MR Imaging DD 001",a1) CS SyncraScanType 1 PrivateTag +(2005,"Philips MR Imaging DD 001",b0) FL DiffusionDirectionRL 1 PrivateTag +(2005,"Philips MR Imaging DD 001",b1) FL DiffusionDirectionAP 1 PrivateTag +(2005,"Philips MR Imaging DD 001",b2) FL DiffusionDirectionFH 1 PrivateTag + +(2005,"Philips MR Imaging DD 005",02) SQ Unknown 1 PrivateTag + +# Note: Some Philips devices use these private tags with reservation value +# "Philips MR Imaging DD 001", others use "PHILIPS MR IMAGING DD 001". All attributes +# should thus be present twice in this dictionary, once for each spelling variant. +# +(2005,"PHILIPS MR IMAGING DD 001",05) CS SynergyReconstructionType 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",1e) SH MIPProtocol 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",1f) SH MPRProtocol 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",20) SL NumberOfChemicalShifts 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",2d) SS NumberOfStackSlices 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",83) SQ Unknown 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",a1) CS SyncraScanType 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",b0) FL DiffusionDirectionRL 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",b1) FL DiffusionDirectionAP 1 PrivateTag +(2005,"PHILIPS MR IMAGING DD 001",b2) FL DiffusionDirectionFH 1 PrivateTag + +(0019,"PHILIPS MR R5.5/PART",1000) DS FieldOfView 1 PrivateTag +(0019,"PHILIPS MR R5.6/PART",1000) DS FieldOfView 1 PrivateTag + +(0019,"PHILIPS MR SPECTRO;1",01) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",02) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",03) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",04) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",05) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",06) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",07) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",08) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",09) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",10) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",12) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",13) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",14) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",15) US Unknown 1-n PrivateTag +(0019,"PHILIPS MR SPECTRO;1",16) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",17) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",18) UN Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",20) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",21) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",22) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",23) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",24) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",25) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",26) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",27) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",28) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",29) IS Unknown 1-n PrivateTag +(0019,"PHILIPS MR SPECTRO;1",31) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",32) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",41) LT Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",42) IS Unknown 2 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",43) IS Unknown 2 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",45) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",46) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",47) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",48) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",49) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",50) UN Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",60) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",61) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",70) UN Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",71) IS Unknown 1-n PrivateTag +(0019,"PHILIPS MR SPECTRO;1",72) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",73) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",74) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",76) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",77) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",78) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",79) US Unknown 1 PrivateTag +(0019,"PHILIPS MR SPECTRO;1",80) IS Unknown 1 PrivateTag + +(0009,"PHILIPS MR",10) LO SPIRelease 1 PrivateTag +(0009,"PHILIPS MR",12) LO Unknown 1 PrivateTag + +(0019,"PHILIPS MR/LAST",09) DS MainMagneticField 1 PrivateTag +(0019,"PHILIPS MR/LAST",0e) IS FlowCompensation 1 PrivateTag +(0019,"PHILIPS MR/LAST",b1) IS MinimumRRInterval 1 PrivateTag +(0019,"PHILIPS MR/LAST",b2) IS MaximumRRInterval 1 PrivateTag +(0019,"PHILIPS MR/LAST",b3) IS NumberOfRejections 1 PrivateTag +(0019,"PHILIPS MR/LAST",b4) IS NumberOfRRIntervals 1-n PrivateTag +(0019,"PHILIPS MR/LAST",b5) IS ArrhythmiaRejection 1 PrivateTag +(0019,"PHILIPS MR/LAST",c0) DS Unknown 1-n PrivateTag +(0019,"PHILIPS MR/LAST",c6) IS CycledMultipleSlice 1 PrivateTag +(0019,"PHILIPS MR/LAST",ce) IS REST 1 PrivateTag +(0019,"PHILIPS MR/LAST",d5) DS Unknown 1 PrivateTag +(0019,"PHILIPS MR/LAST",d6) IS FourierInterpolation 1 PrivateTag +(0019,"PHILIPS MR/LAST",d9) IS Unknown 1-n PrivateTag +(0019,"PHILIPS MR/LAST",e0) IS Prepulse 1 PrivateTag +(0019,"PHILIPS MR/LAST",e1) DS PrepulseDelay 1 PrivateTag +(0019,"PHILIPS MR/LAST",e2) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/LAST",e3) DS Unknown 1 PrivateTag +(0019,"PHILIPS MR/LAST",f0) LT WSProtocolString1 1 PrivateTag +(0019,"PHILIPS MR/LAST",f1) LT WSProtocolString2 1 PrivateTag +(0019,"PHILIPS MR/LAST",f2) LT WSProtocolString3 1 PrivateTag +(0019,"PHILIPS MR/LAST",f3) LT WSProtocolString4 1 PrivateTag +(0021,"PHILIPS MR/LAST",00) IS Unknown 1 PrivateTag +(0021,"PHILIPS MR/LAST",10) IS Unknown 1 PrivateTag +(0021,"PHILIPS MR/LAST",20) IS Unknown 1 PrivateTag +(0021,"PHILIPS MR/LAST",21) DS SliceGap 1 PrivateTag +(0021,"PHILIPS MR/LAST",22) DS StackRadialAngle 1 PrivateTag +(0027,"PHILIPS MR/LAST",00) US Unknown 1 PrivateTag +(0027,"PHILIPS MR/LAST",11) US Unknown 1-n PrivateTag +(0027,"PHILIPS MR/LAST",12) DS Unknown 1-n PrivateTag +(0027,"PHILIPS MR/LAST",13) DS Unknown 1-n PrivateTag +(0027,"PHILIPS MR/LAST",14) DS Unknown 1-n PrivateTag +(0027,"PHILIPS MR/LAST",15) DS Unknown 1-n PrivateTag +(0027,"PHILIPS MR/LAST",16) LO Unknown 1 PrivateTag +(0029,"PHILIPS MR/LAST",10) DS FPMin 1 PrivateTag +(0029,"PHILIPS MR/LAST",20) DS FPMax 1 PrivateTag +(0029,"PHILIPS MR/LAST",30) DS ScaledMinimum 1 PrivateTag +(0029,"PHILIPS MR/LAST",40) DS ScaledMaximum 1 PrivateTag +(0029,"PHILIPS MR/LAST",50) DS WindowMinimum 1 PrivateTag +(0029,"PHILIPS MR/LAST",60) DS WindowMaximum 1 PrivateTag +(0029,"PHILIPS MR/LAST",61) IS Unknown 1 PrivateTag +(0029,"PHILIPS MR/LAST",70) DS Unknown 1 PrivateTag +(0029,"PHILIPS MR/LAST",71) DS Unknown 1 PrivateTag +(0029,"PHILIPS MR/LAST",72) IS Unknown 1 PrivateTag +(0029,"PHILIPS MR/LAST",80) IS ViewCenter 1 PrivateTag +(0029,"PHILIPS MR/LAST",81) IS ViewSize 1 PrivateTag +(0029,"PHILIPS MR/LAST",82) IS ViewZoom 1 PrivateTag +(0029,"PHILIPS MR/LAST",83) IS ViewTransform 1 PrivateTag +(6001,"PHILIPS MR/LAST",00) LT Unknown 1 PrivateTag + +(0019,"PHILIPS MR/PART",1000) DS FieldOfView 1 PrivateTag +(0019,"PHILIPS MR/PART",1005) DS CCAngulation 1 PrivateTag +(0019,"PHILIPS MR/PART",1006) DS APAngulation 1 PrivateTag +(0019,"PHILIPS MR/PART",1007) DS LRAngulation 1 PrivateTag +(0019,"PHILIPS MR/PART",1008) IS PatientPosition 1 PrivateTag +(0019,"PHILIPS MR/PART",1009) IS PatientOrientation 1 PrivateTag +(0019,"PHILIPS MR/PART",100a) IS SliceOrientation 1 PrivateTag +(0019,"PHILIPS MR/PART",100b) DS LROffcenter 1 PrivateTag +(0019,"PHILIPS MR/PART",100c) DS CCOffcenter 1 PrivateTag +(0019,"PHILIPS MR/PART",100d) DS APOffcenter 1 PrivateTag +(0019,"PHILIPS MR/PART",100e) DS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",100f) IS NumberOfSlices 1 PrivateTag +(0019,"PHILIPS MR/PART",1010) DS SliceFactor 1 PrivateTag +(0019,"PHILIPS MR/PART",1011) DS EchoTimes 1-n PrivateTag +(0019,"PHILIPS MR/PART",1015) IS DynamicStudy 1 PrivateTag +(0019,"PHILIPS MR/PART",1018) DS HeartbeatInterval 1 PrivateTag +(0019,"PHILIPS MR/PART",1019) DS RepetitionTimeFFE 1 PrivateTag +(0019,"PHILIPS MR/PART",101a) DS FFEFlipAngle 1 PrivateTag +(0019,"PHILIPS MR/PART",101b) IS NumberOfScans 1 PrivateTag +(0019,"PHILIPS MR/PART",1021) DS Unknown 1-n PrivateTag +(0019,"PHILIPS MR/PART",1022) DS DynamicScanTimeBegin 1 PrivateTag +(0019,"PHILIPS MR/PART",1024) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",1064) DS RepetitionTimeSE 1 PrivateTag +(0019,"PHILIPS MR/PART",1065) DS RepetitionTimeIR 1 PrivateTag +(0019,"PHILIPS MR/PART",1069) IS NumberOfPhases 1 PrivateTag +(0019,"PHILIPS MR/PART",106a) IS CardiacFrequency 1 PrivateTag +(0019,"PHILIPS MR/PART",106b) DS InversionDelay 1 PrivateTag +(0019,"PHILIPS MR/PART",106c) DS GateDelay 1 PrivateTag +(0019,"PHILIPS MR/PART",106d) DS GateWidth 1 PrivateTag +(0019,"PHILIPS MR/PART",106e) DS TriggerDelayTime 1 PrivateTag +(0019,"PHILIPS MR/PART",1080) IS NumberOfChemicalShifts 1 PrivateTag +(0019,"PHILIPS MR/PART",1081) DS ChemicalShift 1 PrivateTag +(0019,"PHILIPS MR/PART",1084) IS NumberOfRows 1 PrivateTag +(0019,"PHILIPS MR/PART",1085) IS NumberOfSamples 1 PrivateTag +(0019,"PHILIPS MR/PART",1094) LO MagnetizationTransferContrast 1 PrivateTag +(0019,"PHILIPS MR/PART",1095) LO SpectralPresaturationWithInversionRecovery 1 PrivateTag +(0019,"PHILIPS MR/PART",1096) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",1097) LO Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10a0) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10a1) DS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10a3) DS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10a4) CS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10c8) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10c9) IS FoldoverDirectionTransverse 1 PrivateTag +(0019,"PHILIPS MR/PART",10ca) IS FoldoverDirectionSagittal 1 PrivateTag +(0019,"PHILIPS MR/PART",10cb) IS FoldoverDirectionCoronal 1 PrivateTag +(0019,"PHILIPS MR/PART",10cc) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10cd) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10ce) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10cf) IS NumberOfEchoes 1 PrivateTag +(0019,"PHILIPS MR/PART",10d0) IS ScanResolution 1 PrivateTag +(0019,"PHILIPS MR/PART",10d2) LO WaterFatShift 2 PrivateTag +(0019,"PHILIPS MR/PART",10d4) IS ArtifactReduction 1 PrivateTag +(0019,"PHILIPS MR/PART",10d5) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10d6) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10d7) DS ScanPercentage 1 PrivateTag +(0019,"PHILIPS MR/PART",10d8) IS Halfscan 1 PrivateTag +(0019,"PHILIPS MR/PART",10d9) IS EPIFactor 1 PrivateTag +(0019,"PHILIPS MR/PART",10da) IS TurboFactor 1 PrivateTag +(0019,"PHILIPS MR/PART",10db) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",10e0) IS PercentageOfScanCompleted 1 PrivateTag +(0019,"PHILIPS MR/PART",10e1) IS Unknown 1 PrivateTag +(0019,"PHILIPS MR/PART",1100) IS NumberOfStacks 1 PrivateTag +(0019,"PHILIPS MR/PART",1101) IS StackType 1-n PrivateTag +(0019,"PHILIPS MR/PART",1102) IS Unknown 1-n PrivateTag +(0019,"PHILIPS MR/PART",110b) DS LROffcenter 1 PrivateTag +(0019,"PHILIPS MR/PART",110c) DS CCOffcenter 1 PrivateTag +(0019,"PHILIPS MR/PART",110d) DS APOffcenter 1 PrivateTag +(0019,"PHILIPS MR/PART",1145) IS ReconstructionResolution 1 PrivateTag +(0019,"PHILIPS MR/PART",11fc) IS ResonanceFrequency 1 PrivateTag +(0019,"PHILIPS MR/PART",12c0) DS TriggerDelayTimes 1 PrivateTag +(0019,"PHILIPS MR/PART",12e0) IS PrepulseType 1 PrivateTag +(0019,"PHILIPS MR/PART",12e1) DS PrepulseDelay 1 PrivateTag +(0019,"PHILIPS MR/PART",12e3) DS PhaseContrastVelocity 1 PrivateTag +(0021,"PHILIPS MR/PART",1000) IS ReconstructionNumber 1 PrivateTag +(0021,"PHILIPS MR/PART",1010) IS ImageType 1 PrivateTag +(0021,"PHILIPS MR/PART",1020) IS SliceNumber 1 PrivateTag +(0021,"PHILIPS MR/PART",1030) IS EchoNumber 1 PrivateTag +(0021,"PHILIPS MR/PART",1031) DS PatientReferenceID 1 PrivateTag +(0021,"PHILIPS MR/PART",1035) IS ChemicalShiftNumber 1 PrivateTag +(0021,"PHILIPS MR/PART",1040) IS PhaseNumber 1 PrivateTag +(0021,"PHILIPS MR/PART",1050) IS DynamicScanNumber 1 PrivateTag +(0021,"PHILIPS MR/PART",1060) IS NumberOfRowsInObject 1 PrivateTag +(0021,"PHILIPS MR/PART",1061) IS RowNumber 1-n PrivateTag +(0021,"PHILIPS MR/PART",1062) IS Unknown 1-n PrivateTag +(0021,"PHILIPS MR/PART",1100) DA ScanDate 1 PrivateTag +(0021,"PHILIPS MR/PART",1110) TM ScanTime 1 PrivateTag +(0021,"PHILIPS MR/PART",1221) IS SliceGap 1 PrivateTag +(0029,"PHILIPS MR/PART",00) DS Unknown 2 PrivateTag +(0029,"PHILIPS MR/PART",04) US Unknown 1 PrivateTag +(0029,"PHILIPS MR/PART",10) DS Unknown 1 PrivateTag +(0029,"PHILIPS MR/PART",11) DS Unknown 1 PrivateTag +(0029,"PHILIPS MR/PART",20) LO Unknown 1 PrivateTag +(0029,"PHILIPS MR/PART",31) DS Unknown 2 PrivateTag +(0029,"PHILIPS MR/PART",32) DS Unknown 2 PrivateTag +(0029,"PHILIPS MR/PART",c3) IS ScanResolution 1 PrivateTag +(0029,"PHILIPS MR/PART",c4) IS FieldOfView 1 PrivateTag +(0029,"PHILIPS MR/PART",d5) LT SliceThickness 1 PrivateTag + +(0019,"PHILIPS-MR-1",11) IS ChemicalShiftNumber 1 PrivateTag +(0019,"PHILIPS-MR-1",12) IS PhaseNumber 1 PrivateTag +(0021,"PHILIPS-MR-1",01) IS ReconstructionNumber 1 PrivateTag +(0021,"PHILIPS-MR-1",02) IS SliceNumber 1 PrivateTag + +(7001,"Picker NM Private Group",01) UI Unknown 1 PrivateTag +(7001,"Picker NM Private Group",02) OB Unknown 1 PrivateTag + +(0019,"SIEMENS CM VA0 ACQU",10) LT ParameterFileName 1 PrivateTag +(0019,"SIEMENS CM VA0 ACQU",11) LO SequenceFileName 1 PrivateTag +(0019,"SIEMENS CM VA0 ACQU",12) LT SequenceFileOwner 1 PrivateTag +(0019,"SIEMENS CM VA0 ACQU",13) LT SequenceDescription 1 PrivateTag +(0019,"SIEMENS CM VA0 ACQU",14) LT EPIFileName 1 PrivateTag + +(0009,"SIEMENS CM VA0 CMS",00) DS NumberOfMeasurements 1 PrivateTag +(0009,"SIEMENS CM VA0 CMS",10) LT StorageMode 1 PrivateTag +(0009,"SIEMENS CM VA0 CMS",12) UL EvaluationMaskImage 1 PrivateTag +(0009,"SIEMENS CM VA0 CMS",26) DA LastMoveDate 1 PrivateTag +(0009,"SIEMENS CM VA0 CMS",27) TM LastMoveTime 1 PrivateTag +(0011,"SIEMENS CM VA0 CMS",0a) LT Unknown 1 PrivateTag +(0011,"SIEMENS CM VA0 CMS",10) DA RegistrationDate 1 PrivateTag +(0011,"SIEMENS CM VA0 CMS",11) TM RegistrationTime 1 PrivateTag +(0011,"SIEMENS CM VA0 CMS",22) LT Unknown 1 PrivateTag +(0011,"SIEMENS CM VA0 CMS",23) DS UsedPatientWeight 1 PrivateTag +(0011,"SIEMENS CM VA0 CMS",40) IS OrganCode 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",00) LT ModifyingPhysician 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",10) DA ModificationDate 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",12) TM ModificationTime 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",20) LO PatientName 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",22) LO PatientId 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",30) DA PatientBirthdate 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",31) DS PatientWeight 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",32) LT PatientsMaidenName 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",33) LT ReferringPhysician 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",34) LT AdmittingDiagnosis 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",35) LO PatientSex 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",40) LO ProcedureDescription 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",42) LO RestDirection 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",44) LO PatientPosition 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",46) LT ViewDirection 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",50) LT Unknown 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",51) LT Unknown 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",52) LT Unknown 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",53) LT Unknown 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",54) LT Unknown 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",55) LT Unknown 1 PrivateTag +(0013,"SIEMENS CM VA0 CMS",56) LT Unknown 1 PrivateTag +(0019,"SIEMENS CM VA0 CMS",10) DS NetFrequency 1 PrivateTag +(0019,"SIEMENS CM VA0 CMS",20) LT MeasurementMode 1 PrivateTag +(0019,"SIEMENS CM VA0 CMS",30) LT CalculationMode 1 PrivateTag +(0019,"SIEMENS CM VA0 CMS",50) IS NoiseLevel 1 PrivateTag +(0019,"SIEMENS CM VA0 CMS",60) IS NumberOfDataBytes 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",20) DS FoV 2 PrivateTag +(0021,"SIEMENS CM VA0 CMS",22) DS ImageMagnificationFactor 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",24) DS ImageScrollOffset 2 PrivateTag +(0021,"SIEMENS CM VA0 CMS",26) IS ImagePixelOffset 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",30) LT ViewDirection 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",32) CS PatientRestDirection 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",60) DS ImagePosition 3 PrivateTag +(0021,"SIEMENS CM VA0 CMS",61) DS ImageNormal 3 PrivateTag +(0021,"SIEMENS CM VA0 CMS",63) DS ImageDistance 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",65) US ImagePositioningHistoryMask 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",6a) DS ImageRow 3 PrivateTag +(0021,"SIEMENS CM VA0 CMS",6b) DS ImageColumn 3 PrivateTag +(0021,"SIEMENS CM VA0 CMS",70) LT PatientOrientationSet1 3 PrivateTag +(0021,"SIEMENS CM VA0 CMS",71) LT PatientOrientationSet2 3 PrivateTag +(0021,"SIEMENS CM VA0 CMS",80) LT StudyName 1 PrivateTag +(0021,"SIEMENS CM VA0 CMS",82) LT StudyType 3 PrivateTag +(0029,"SIEMENS CM VA0 CMS",10) LT WindowStyle 1 PrivateTag +(0029,"SIEMENS CM VA0 CMS",11) LT Unknown 1 PrivateTag +(0029,"SIEMENS CM VA0 CMS",13) LT Unknown 1 PrivateTag +(0029,"SIEMENS CM VA0 CMS",20) LT PixelQualityCode 3 PrivateTag +(0029,"SIEMENS CM VA0 CMS",22) IS PixelQualityValue 3 PrivateTag +(0029,"SIEMENS CM VA0 CMS",50) LT ArchiveCode 1 PrivateTag +(0029,"SIEMENS CM VA0 CMS",51) LT ExposureCode 1 PrivateTag +(0029,"SIEMENS CM VA0 CMS",52) LT SortCode 1 PrivateTag +(0029,"SIEMENS CM VA0 CMS",53) LT Unknown 1 PrivateTag +(0029,"SIEMENS CM VA0 CMS",60) LT Splash 1 PrivateTag +(0051,"SIEMENS CM VA0 CMS",10) LT ImageText 1-n PrivateTag +(6021,"SIEMENS CM VA0 CMS",00) LT ImageGraphicsFormatCode 1 PrivateTag +(6021,"SIEMENS CM VA0 CMS",10) LT ImageGraphics 1 PrivateTag +(7fe1,"SIEMENS CM VA0 CMS",00) OB BinaryData 1-n PrivateTag + +(0009,"SIEMENS CM VA0 LAB",10) LT GeneratorIdentificationLabel 1 PrivateTag +(0009,"SIEMENS CM VA0 LAB",11) LT GantryIdentificationLabel 1 PrivateTag +(0009,"SIEMENS CM VA0 LAB",12) LT X-RayTubeIdentificationLabel 1 PrivateTag +(0009,"SIEMENS CM VA0 LAB",13) LT DetectorIdentificationLabel 1 PrivateTag +(0009,"SIEMENS CM VA0 LAB",14) LT DASIdentificationLabel 1 PrivateTag +(0009,"SIEMENS CM VA0 LAB",15) LT SMIIdentificationLabel 1 PrivateTag +(0009,"SIEMENS CM VA0 LAB",16) LT CPUIdentificationLabel 1 PrivateTag +(0009,"SIEMENS CM VA0 LAB",20) LT HeaderVersion 1 PrivateTag + +(0029,"SIEMENS CSA HEADER",08) CS CSAImageHeaderType 1 PrivateTag +(0029,"SIEMENS CSA HEADER",09) LO CSAImageHeaderVersion 1 PrivateTag +(0029,"SIEMENS CSA HEADER",10) OB CSAImageHeaderInfo 1 PrivateTag +(0029,"SIEMENS CSA HEADER",18) CS CSASeriesHeaderType 1 PrivateTag +(0029,"SIEMENS CSA HEADER",19) LO CSASeriesHeaderVersion 1 PrivateTag +(0029,"SIEMENS CSA HEADER",20) OB CSASeriesHeaderInfo 1 PrivateTag + +(0029,"SIEMENS CSA NON-IMAGE",08) CS CSADataType 1 PrivateTag +(0029,"SIEMENS CSA NON-IMAGE",09) LO CSADataVersion 1 PrivateTag +(0029,"SIEMENS CSA NON-IMAGE",10) OB CSADataInfo 1 PrivateTag +(7FE1,"SIEMENS CSA NON-IMAGE",10) OB CSAData 1 PrivateTag + +(0019,"SIEMENS CT VA0 COAD",10) DS DistanceSourceToSourceSideCollimator 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",11) DS DistanceSourceToDetectorSideCollimator 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",20) IS NumberOfPossibleChannels 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",21) IS MeanChannelNumber 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",22) DS DetectorSpacing 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",23) DS DetectorCenter 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",24) DS ReadingIntegrationTime 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",50) DS DetectorAlignment 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",52) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",54) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",60) DS FocusAlignment 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",65) UL FocalSpotDeflectionAmplitude 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",66) UL FocalSpotDeflectionPhase 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",67) UL FocalSpotDeflectionOffset 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",70) DS WaterScalingFactor 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",71) DS InterpolationFactor 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",80) LT PatientRegion 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",82) LT PatientPhaseOfLife 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",90) DS OsteoOffset 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",92) DS OsteoRegressionLineSlope 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",93) DS OsteoRegressionLineIntercept 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",94) DS OsteoStandardizationCode 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",96) IS OsteoPhantomNumber 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",A3) US Unknown 1-n PrivateTag +(0019,"SIEMENS CT VA0 COAD",A4) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",A5) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",A6) US Unknown 1-n PrivateTag +(0019,"SIEMENS CT VA0 COAD",A7) US Unknown 1-n PrivateTag +(0019,"SIEMENS CT VA0 COAD",A8) US Unknown 1-n PrivateTag +(0019,"SIEMENS CT VA0 COAD",A9) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",AA) LT Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",AB) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",AC) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",AD) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",AE) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",AF) DS Unknown 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",B0) DS FeedPerRotation 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",BD) IS PulmoTriggerLevel 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",BE) DS ExpiratoricReserveVolume 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",BF) DS VitalCapacity 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",C0) DS PulmoWater 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",C1) DS PulmoAir 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",C2) DA PulmoDate 1 PrivateTag +(0019,"SIEMENS CT VA0 COAD",C3) TM PulmoTime 1 PrivateTag + +(0019,"SIEMENS CT VA0 GEN",10) DS SourceSideCollimatorAperture 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",11) DS DetectorSideCollimatorAperture 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",20) DS ExposureTime 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",21) DS ExposureCurrent 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",25) DS KVPGeneratorPowerCurrent 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",26) DS GeneratorVoltage 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",40) UL MasterControlMask 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",42) US ProcessingMask 5 PrivateTag +(0019,"SIEMENS CT VA0 GEN",44) US Unknown 1-n PrivateTag +(0019,"SIEMENS CT VA0 GEN",45) US Unknown 1-n PrivateTag +(0019,"SIEMENS CT VA0 GEN",62) IS NumberOfVirtuellChannels 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",70) IS NumberOfReadings 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",71) LT Unknown 1-n PrivateTag +(0019,"SIEMENS CT VA0 GEN",74) IS NumberOfProjections 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",75) IS NumberOfBytes 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",80) LT ReconstructionAlgorithmSet 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",81) LT ReconstructionAlgorithmIndex 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",82) LT RegenerationSoftwareVersion 1 PrivateTag +(0019,"SIEMENS CT VA0 GEN",88) DS Unknown 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",10) IS RotationAngle 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",11) IS StartAngle 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",20) US Unknown 1-n PrivateTag +(0021,"SIEMENS CT VA0 GEN",30) IS TopogramTubePosition 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",32) DS LengthOfTopogram 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",34) DS TopogramCorrectionFactor 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",36) DS MaximumTablePosition 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",40) IS TableMoveDirectionCode 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",45) IS VOIStartRow 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",46) IS VOIStopRow 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",47) IS VOIStartColumn 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",48) IS VOIStopColumn 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",49) IS VOIStartSlice 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",4a) IS VOIStopSlice 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",50) IS VectorStartRow 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",51) IS VectorRowStep 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",52) IS VectorStartColumn 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",53) IS VectorColumnStep 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",60) IS RangeTypeCode 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",62) IS ReferenceTypeCode 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",70) DS ObjectOrientation 3 PrivateTag +(0021,"SIEMENS CT VA0 GEN",72) DS LightOrientation 3 PrivateTag +(0021,"SIEMENS CT VA0 GEN",75) DS LightBrightness 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",76) DS LightContrast 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",7a) IS OverlayThreshold 2 PrivateTag +(0021,"SIEMENS CT VA0 GEN",7b) IS SurfaceThreshold 2 PrivateTag +(0021,"SIEMENS CT VA0 GEN",7c) IS GreyScaleThreshold 2 PrivateTag +(0021,"SIEMENS CT VA0 GEN",a0) DS Unknown 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",a2) LT Unknown 1 PrivateTag +(0021,"SIEMENS CT VA0 GEN",a7) LT Unknown 1 PrivateTag + +(0009,"SIEMENS CT VA0 IDE",10) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",30) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",31) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",32) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",34) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",40) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",42) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",50) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 IDE",51) LT Unknown 1 PrivateTag + +(0009,"SIEMENS CT VA0 ORI",20) LT Unknown 1 PrivateTag +(0009,"SIEMENS CT VA0 ORI",30) LT Unknown 1 PrivateTag + +(6021,"SIEMENS CT VA0 OST",00) LT OsteoContourComment 1 PrivateTag +(6021,"SIEMENS CT VA0 OST",10) US OsteoContourBuffer 256 PrivateTag + +(0021,"SIEMENS CT VA0 RAW",10) UL CreationMask 2 PrivateTag +(0021,"SIEMENS CT VA0 RAW",20) UL EvaluationMask 2 PrivateTag +(0021,"SIEMENS CT VA0 RAW",30) US ExtendedProcessingMask 7 PrivateTag +(0021,"SIEMENS CT VA0 RAW",40) US Unknown 1-n PrivateTag +(0021,"SIEMENS CT VA0 RAW",41) US Unknown 1-n PrivateTag +(0021,"SIEMENS CT VA0 RAW",42) US Unknown 1-n PrivateTag +(0021,"SIEMENS CT VA0 RAW",43) US Unknown 1-n PrivateTag +(0021,"SIEMENS CT VA0 RAW",44) US Unknown 1-n PrivateTag +(0021,"SIEMENS CT VA0 RAW",50) LT Unknown 1 PrivateTag + +(0009,"SIEMENS DICOM",10) UN Unknown 1 PrivateTag +(0009,"SIEMENS DICOM",12) LT Unknown 1 PrivateTag + +(0019,"SIEMENS DLR.01",10) LT MeasurementMode 1 PrivateTag +(0019,"SIEMENS DLR.01",11) LT ImageType 1 PrivateTag +(0019,"SIEMENS DLR.01",15) LT SoftwareVersion 1 PrivateTag +(0019,"SIEMENS DLR.01",20) LT MPMCode 1 PrivateTag +(0019,"SIEMENS DLR.01",21) LT Latitude 1 PrivateTag +(0019,"SIEMENS DLR.01",22) LT Sensitivity 1 PrivateTag +(0019,"SIEMENS DLR.01",23) LT EDR 1 PrivateTag +(0019,"SIEMENS DLR.01",24) LT LFix 1 PrivateTag +(0019,"SIEMENS DLR.01",25) LT SFix 1 PrivateTag +(0019,"SIEMENS DLR.01",26) LT PresetMode 1 PrivateTag +(0019,"SIEMENS DLR.01",27) LT Region 1 PrivateTag +(0019,"SIEMENS DLR.01",28) LT Subregion 1 PrivateTag +(0019,"SIEMENS DLR.01",30) LT Orientation 1 PrivateTag +(0019,"SIEMENS DLR.01",31) LT MarkOnFilm 1 PrivateTag +(0019,"SIEMENS DLR.01",32) LT RotationOnDRC 1 PrivateTag +(0019,"SIEMENS DLR.01",40) LT ReaderType 1 PrivateTag +(0019,"SIEMENS DLR.01",41) LT SubModality 1 PrivateTag +(0019,"SIEMENS DLR.01",42) LT ReaderSerialNumber 1 PrivateTag +(0019,"SIEMENS DLR.01",50) LT CassetteScale 1 PrivateTag +(0019,"SIEMENS DLR.01",51) LT CassetteMatrix 1 PrivateTag +(0019,"SIEMENS DLR.01",52) LT CassetteSubmatrix 1 PrivateTag +(0019,"SIEMENS DLR.01",53) LT Barcode 1 PrivateTag +(0019,"SIEMENS DLR.01",60) LT ContrastType 1 PrivateTag +(0019,"SIEMENS DLR.01",61) LT RotationAmount 1 PrivateTag +(0019,"SIEMENS DLR.01",62) LT RotationCenter 1 PrivateTag +(0019,"SIEMENS DLR.01",63) LT DensityShift 1 PrivateTag +(0019,"SIEMENS DLR.01",64) US FrequencyRank 1 PrivateTag +(0019,"SIEMENS DLR.01",65) LT FrequencyEnhancement 1 PrivateTag +(0019,"SIEMENS DLR.01",66) LT FrequencyType 1 PrivateTag +(0019,"SIEMENS DLR.01",67) LT KernelLength 1 PrivateTag +(0019,"SIEMENS DLR.01",68) UL KernelMode 1 PrivateTag +(0019,"SIEMENS DLR.01",69) UL ConvolutionMode 1 PrivateTag +(0019,"SIEMENS DLR.01",70) LT PLASource 1 PrivateTag +(0019,"SIEMENS DLR.01",71) LT PLADestination 1 PrivateTag +(0019,"SIEMENS DLR.01",75) LT UIDOriginalImage 1 PrivateTag +(0019,"SIEMENS DLR.01",76) LT Unknown 1 PrivateTag +(0019,"SIEMENS DLR.01",80) LT ReaderHeader 1 PrivateTag +(0019,"SIEMENS DLR.01",90) LT PLAOfSecondaryDestination 1 PrivateTag +(0019,"SIEMENS DLR.01",a0) DS Unknown 1 PrivateTag +(0019,"SIEMENS DLR.01",a1) DS Unknown 1 PrivateTag +(0041,"SIEMENS DLR.01",10) US NumberOfHardcopies 1 PrivateTag +(0041,"SIEMENS DLR.01",20) LT FilmFormat 1 PrivateTag +(0041,"SIEMENS DLR.01",30) LT FilmSize 1 PrivateTag +(0041,"SIEMENS DLR.01",31) LT FullFilmFormat 1 PrivateTag + +(0003,"SIEMENS ISI",08) US ISICommandField 1 PrivateTag +(0003,"SIEMENS ISI",11) US AttachIDApplicationCode 1 PrivateTag +(0003,"SIEMENS ISI",12) UL AttachIDMessageCount 1 PrivateTag +(0003,"SIEMENS ISI",13) DA AttachIDDate 1 PrivateTag +(0003,"SIEMENS ISI",14) TM AttachIDTime 1 PrivateTag +(0003,"SIEMENS ISI",20) US MessageType 1 PrivateTag +(0003,"SIEMENS ISI",30) DA MaxWaitingDate 1 PrivateTag +(0003,"SIEMENS ISI",31) TM MaxWaitingTime 1 PrivateTag +(0009,"SIEMENS ISI",01) UN RISPatientInfoIMGEF 1 PrivateTag +(0011,"SIEMENS ISI",03) LT PatientUID 1 PrivateTag +(0011,"SIEMENS ISI",04) LT PatientID 1 PrivateTag +(0011,"SIEMENS ISI",0a) LT CaseID 1 PrivateTag +(0011,"SIEMENS ISI",22) LT RequestID 1 PrivateTag +(0011,"SIEMENS ISI",23) LT ExaminationUID 1 PrivateTag +(0011,"SIEMENS ISI",a1) DA PatientRegistrationDate 1 PrivateTag +(0011,"SIEMENS ISI",a2) TM PatientRegistrationTime 1 PrivateTag +(0011,"SIEMENS ISI",b0) LT PatientLastName 1 PrivateTag +(0011,"SIEMENS ISI",b2) LT PatientFirstName 1 PrivateTag +(0011,"SIEMENS ISI",b4) LT PatientHospitalStatus 1 PrivateTag +(0011,"SIEMENS ISI",bc) TM CurrentLocationTime 1 PrivateTag +(0011,"SIEMENS ISI",c0) LT PatientInsuranceStatus 1 PrivateTag +(0011,"SIEMENS ISI",d0) LT PatientBillingType 1 PrivateTag +(0011,"SIEMENS ISI",d2) LT PatientBillingAddress 1 PrivateTag +(0031,"SIEMENS ISI",12) LT ExaminationReason 1 PrivateTag +(0031,"SIEMENS ISI",30) DA RequestedDate 1 PrivateTag +(0031,"SIEMENS ISI",32) TM WorklistRequestStartTime 1 PrivateTag +(0031,"SIEMENS ISI",33) TM WorklistRequestEndTime 1 PrivateTag +(0031,"SIEMENS ISI",4a) TM RequestedTime 1 PrivateTag +(0031,"SIEMENS ISI",80) LT RequestedLocation 1 PrivateTag +(0055,"SIEMENS ISI",46) LT CurrentWard 1 PrivateTag +(0193,"SIEMENS ISI",02) DS RISKey 1 PrivateTag +(0307,"SIEMENS ISI",01) UN RISWorklistIMGEF 1 PrivateTag +(0309,"SIEMENS ISI",01) UN RISReportIMGEF 1 PrivateTag +(4009,"SIEMENS ISI",01) LT ReportID 1 PrivateTag +(4009,"SIEMENS ISI",20) LT ReportStatus 1 PrivateTag +(4009,"SIEMENS ISI",30) DA ReportCreationDate 1 PrivateTag +(4009,"SIEMENS ISI",70) LT ReportApprovingPhysician 1 PrivateTag +(4009,"SIEMENS ISI",e0) LT ReportText 1 PrivateTag +(4009,"SIEMENS ISI",e1) LT ReportAuthor 1 PrivateTag +(4009,"SIEMENS ISI",e3) LT ReportingRadiologist 1 PrivateTag + +(0029,"SIEMENS MED DISPLAY",04) LT PhotometricInterpretation 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",10) US RowsOfSubmatrix 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",11) US ColumnsOfSubmatrix 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",20) US Unknown 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",21) US Unknown 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",50) US OriginOfSubmatrix 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",99) LT ShutterType 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",a0) US RowsOfRectangularShutter 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",a1) US ColumnsOfRectangularShutter 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",a2) US OriginOfRectangularShutter 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",b0) US RadiusOfCircularShutter 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",b2) US OriginOfCircularShutter 1 PrivateTag +(0029,"SIEMENS MED DISPLAY",c1) US ContourOfIrregularShutter 1 PrivateTag + +(0029,"SIEMENS MED HG",10) US ListOfGroupNumbers 1 PrivateTag +(0029,"SIEMENS MED HG",15) LT ListOfShadowOwnerCodes 1 PrivateTag +(0029,"SIEMENS MED HG",20) US ListOfElementNumbers 1 PrivateTag +(0029,"SIEMENS MED HG",30) US ListOfTotalDisplayLength 1 PrivateTag +(0029,"SIEMENS MED HG",40) LT ListOfDisplayPrefix 1 PrivateTag +(0029,"SIEMENS MED HG",50) LT ListOfDisplayPostfix 1 PrivateTag +(0029,"SIEMENS MED HG",60) US ListOfTextPosition 1 PrivateTag +(0029,"SIEMENS MED HG",70) LT ListOfTextConcatenation 1 PrivateTag +(0029,"SIEMENS MED MG",10) US ListOfGroupNumbers 1 PrivateTag +(0029,"SIEMENS MED MG",15) LT ListOfShadowOwnerCodes 1 PrivateTag +(0029,"SIEMENS MED MG",20) US ListOfElementNumbers 1 PrivateTag +(0029,"SIEMENS MED MG",30) US ListOfTotalDisplayLength 1 PrivateTag +(0029,"SIEMENS MED MG",40) LT ListOfDisplayPrefix 1 PrivateTag +(0029,"SIEMENS MED MG",50) LT ListOfDisplayPostfix 1 PrivateTag +(0029,"SIEMENS MED MG",60) US ListOfTextPosition 1 PrivateTag +(0029,"SIEMENS MED MG",70) LT ListOfTextConcatenation 1 PrivateTag + +(0009,"SIEMENS MED",10) LO RecognitionCode 1 PrivateTag +(0009,"SIEMENS MED",30) UL ByteOffsetOfOriginalHeader 1 PrivateTag +(0009,"SIEMENS MED",31) UL LengthOfOriginalHeader 1 PrivateTag +(0009,"SIEMENS MED",40) UL ByteOffsetOfPixelmatrix 1 PrivateTag +(0009,"SIEMENS MED",41) UL LengthOfPixelmatrixInBytes 1 PrivateTag +(0009,"SIEMENS MED",50) LT Unknown 1 PrivateTag +(0009,"SIEMENS MED",51) LT Unknown 1 PrivateTag +(0009,"SIEMENS MED",f5) LT PDMEFIDPlaceholder 1 PrivateTag +(0009,"SIEMENS MED",f6) LT PDMDataObjectTypeExtension 1 PrivateTag +(0021,"SIEMENS MED",10) DS Zoom 1 PrivateTag +(0021,"SIEMENS MED",11) DS Target 2 PrivateTag +(0021,"SIEMENS MED",12) IS TubeAngle 1 PrivateTag +(0021,"SIEMENS MED",20) US ROIMask 1 PrivateTag +(7001,"SIEMENS MED",10) LT Dummy 1 PrivateTag +(7003,"SIEMENS MED",10) LT Header 1 PrivateTag +(7005,"SIEMENS MED",10) LT Dummy 1 PrivateTag + +(0029,"SIEMENS MEDCOM HEADER",08) CS MedComHeaderType 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",09) LO MedComHeaderVersion 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",10) OB MedComHeaderInfo 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",20) OB MedComHistoryInformation 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",31) LO PMTFInformation1 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",32) UL PMTFInformation2 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",33) UL PMTFInformation3 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",34) CS PMTFInformation4 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",35) UL PMTFInformation5 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",40) SQ ApplicationHeaderSequence 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",41) CS ApplicationHeaderType 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",42) LO ApplicationHeaderID 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",43) LO ApplicationHeaderVersion 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",44) OB ApplicationHeaderInfo 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",50) LO WorkflowControlFlags 8 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",51) CS ArchiveManagementFlagKeepOnline 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",52) CS ArchiveManagementFlagDoNotArchive 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",53) CS ImageLocationStatus 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",54) DS EstimatedRetrieveTime 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",55) DS DataSizeOfRetrievedImages 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",70) SQ SiemensLinkSequence 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",71) AT ReferencedTag 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",72) CS ReferencedTagType 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",73) UL ReferencedValueLength 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",74) CS ReferencedObjectDeviceType 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",75) OB ReferencedObjectDeviceLocation 1 PrivateTag +(0029,"SIEMENS MEDCOM HEADER",76) OB ReferencedObjectDeviceID 1 PrivateTag + +(0029,"SIEMENS MEDCOM HEADER2",60) LO SeriesWorkflowStatus 1 PrivateTag + +(0029,"SIEMENS MEDCOM OOG",08) CS MEDCOMOOGType 1 PrivateTag +(0029,"SIEMENS MEDCOM OOG",09) LO MEDCOMOOGVersion 1 PrivateTag +(0029,"SIEMENS MEDCOM OOG",10) OB MEDCOMOOGInfo 1 PrivateTag + +(0019,"SIEMENS MR VA0 COAD",12) DS MagneticFieldStrength 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",14) DS ADCVoltage 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",16) DS ADCOffset 2 PrivateTag +(0019,"SIEMENS MR VA0 COAD",20) DS TransmitterAmplitude 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",21) IS NumberOfTransmitterAmplitudes 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",22) DS TransmitterAttenuator 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",24) DS TransmitterCalibration 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",26) DS TransmitterReference 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",50) DS ReceiverTotalGain 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",51) DS ReceiverAmplifierGain 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",52) DS ReceiverPreamplifierGain 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",54) DS ReceiverCableAttenuation 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",55) DS ReceiverReferenceGain 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",56) DS ReceiverFilterFrequency 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",60) DS ReconstructionScaleFactor 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",62) DS ReferenceScaleFactor 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",70) DS PhaseGradientAmplitude 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",71) DS ReadoutGradientAmplitude 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",72) DS SelectionGradientAmplitude 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",80) DS GradientDelayTime 3 PrivateTag +(0019,"SIEMENS MR VA0 COAD",82) DS TotalGradientDelayTime 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",90) LT SensitivityCorrectionLabel 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",91) DS SaturationPhaseEncodingVectorCoronalComponent 6 PrivateTag +(0019,"SIEMENS MR VA0 COAD",92) DS SaturationReadoutVectorCoronalComponent 6 PrivateTag +(0019,"SIEMENS MR VA0 COAD",a0) US RFWatchdogMask 3 PrivateTag +(0019,"SIEMENS MR VA0 COAD",a1) DS EPIReconstructionSlope 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",a2) DS RFPowerErrorIndicator 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",a5) DS SpecificAbsorptionRateWholeBody 3 PrivateTag +(0019,"SIEMENS MR VA0 COAD",a6) DS SpecificEnergyDose 3 PrivateTag +(0019,"SIEMENS MR VA0 COAD",b0) UL AdjustmentStatusMask 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",c1) DS EPICapacity 6 PrivateTag +(0019,"SIEMENS MR VA0 COAD",c2) DS EPIInductance 3 PrivateTag +(0019,"SIEMENS MR VA0 COAD",c3) IS EPISwitchConfigurationCode 1-n PrivateTag +(0019,"SIEMENS MR VA0 COAD",c4) IS EPISwitchHardwareCode 1-n PrivateTag +(0019,"SIEMENS MR VA0 COAD",c5) DS EPISwitchDelayTime 1-n PrivateTag +(0019,"SIEMENS MR VA0 COAD",d1) DS FlowSensitivity 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d2) LT CalculationSubmode 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d3) DS FieldOfViewRatio 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d4) IS BaseRawMatrixSize 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d5) IS 2DOversamplingLines 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d6) IS 3DPhaseOversamplingPartitions 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d7) IS EchoLinePosition 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d8) IS EchoColumnPosition 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",d9) IS LinesPerSegment 1 PrivateTag +(0019,"SIEMENS MR VA0 COAD",da) LT PhaseCodingDirection 1 PrivateTag + +(0019,"SIEMENS MR VA0 GEN",10) DS TotalMeasurementTimeNominal 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",11) DS TotalMeasurementTimeCurrent 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",12) DS StartDelayTime 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",13) DS DwellTime 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",14) IS NumberOfPhases 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",16) UL SequenceControlMask 2 PrivateTag +(0019,"SIEMENS MR VA0 GEN",18) UL MeasurementStatusMask 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",20) IS NumberOfFourierLinesNominal 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",21) IS NumberOfFourierLinesCurrent 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",26) IS NumberOfFourierLinesAfterZero 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",28) IS FirstMeasuredFourierLine 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",30) IS AcquisitionColumns 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",31) IS ReconstructionColumns 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",40) IS ArrayCoilElementNumber 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",41) UL ArrayCoilElementSelectMask 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",42) UL ArrayCoilElementDataMask 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",43) IS ArrayCoilElementToADCConnect 1-n PrivateTag +(0019,"SIEMENS MR VA0 GEN",44) DS ArrayCoilElementNoiseLevel 1-n PrivateTag +(0019,"SIEMENS MR VA0 GEN",45) IS ArrayCoilADCPairNumber 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",46) UL ArrayCoilCombinationMask 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",50) IS NumberOfAverages 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",60) DS FlipAngle 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",70) IS NumberOfPrescans 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",81) LT FilterTypeForRawData 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",82) DS FilterParameterForRawData 1-n PrivateTag +(0019,"SIEMENS MR VA0 GEN",83) LT FilterTypeForImageData 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",84) DS FilterParameterForImageData 1-n PrivateTag +(0019,"SIEMENS MR VA0 GEN",85) LT FilterTypeForPhaseCorrection 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",86) DS FilterParameterForPhaseCorrection 1-n PrivateTag +(0019,"SIEMENS MR VA0 GEN",87) LT NormalizationFilterTypeForImageData 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",88) DS NormalizationFilterParameterForImageData 1-n PrivateTag +(0019,"SIEMENS MR VA0 GEN",90) IS NumberOfSaturationRegions 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",91) DS SaturationPhaseEncodingVectorSagittalComponent 6 PrivateTag +(0019,"SIEMENS MR VA0 GEN",92) DS SaturationReadoutVectorSagittalComponent 6 PrivateTag +(0019,"SIEMENS MR VA0 GEN",93) DS EPIStimulationMonitorMode 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",94) DS ImageRotationAngle 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",96) UL CoilIDMask 3 PrivateTag +(0019,"SIEMENS MR VA0 GEN",97) UL CoilClassMask 2 PrivateTag +(0019,"SIEMENS MR VA0 GEN",98) DS CoilPosition 3 PrivateTag +(0019,"SIEMENS MR VA0 GEN",a0) DS EPIReconstructionPhase 1 PrivateTag +(0019,"SIEMENS MR VA0 GEN",a1) DS EPIReconstructionSlope 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",20) IS PhaseCorrectionRowsSequence 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",21) IS PhaseCorrectionColumnsSequence 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",22) IS PhaseCorrectionRowsReconstruction 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",24) IS PhaseCorrectionColumnsReconstruction 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",30) IS NumberOf3DRawPartitionsNominal 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",31) IS NumberOf3DRawPartitionsCurrent 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",34) IS NumberOf3DImagePartitions 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",36) IS Actual3DImagePartitionNumber 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",39) DS SlabThickness 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",40) IS NumberOfSlicesNominal 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",41) IS NumberOfSlicesCurrent 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",42) IS CurrentSliceNumber 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",43) IS CurrentGroupNumber 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",44) DS CurrentSliceDistanceFactor 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",45) IS MIPStartRow 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",46) IS MIPStopRow 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",47) IS MIPStartColumn 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",48) IS MIPStartColumn 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",49) IS MIPStartSlice Name= 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",4a) IS MIPStartSlice 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",4f) LT OrderofSlices 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",50) US SignalMask 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",52) DS DelayAfterTrigger 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",53) IS RRInterval 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",54) DS NumberOfTriggerPulses 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",56) DS RepetitionTimeEffective 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",57) LT GatePhase 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",58) DS GateThreshold 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",59) DS GatedRatio 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",60) IS NumberOfInterpolatedImages 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",70) IS NumberOfEchoes 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",72) DS SecondEchoTime 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",73) DS SecondRepetitionTime 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",80) IS CardiacCode 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",91) DS SaturationPhaseEncodingVectorTransverseComponent 6 PrivateTag +(0021,"SIEMENS MR VA0 GEN",92) DS SaturationReadoutVectorTransverseComponent 6 PrivateTag +(0021,"SIEMENS MR VA0 GEN",93) DS EPIChangeValueOfMagnitude 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",94) DS EPIChangeValueOfXComponent 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",95) DS EPIChangeValueOfYComponent 1 PrivateTag +(0021,"SIEMENS MR VA0 GEN",96) DS EPIChangeValueOfZComponent 1 PrivateTag + +(0021,"SIEMENS MR VA0 RAW",00) LT SequenceType 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",01) IS VectorSizeOriginal 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",02) IS VectorSizeExtended 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",03) DS AcquiredSpectralRange 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",04) DS VOIPosition 3 PrivateTag +(0021,"SIEMENS MR VA0 RAW",05) DS VOISize 3 PrivateTag +(0021,"SIEMENS MR VA0 RAW",06) IS CSIMatrixSizeOriginal 3 PrivateTag +(0021,"SIEMENS MR VA0 RAW",07) IS CSIMatrixSizeExtended 3 PrivateTag +(0021,"SIEMENS MR VA0 RAW",08) DS SpatialGridShift 3 PrivateTag +(0021,"SIEMENS MR VA0 RAW",09) DS SignalLimitsMinimum 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",10) DS SignalLimitsMaximum 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",11) DS SpecInfoMask 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",12) DS EPITimeRateOfChangeOfMagnitude 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",13) DS EPITimeRateOfChangeOfXComponent 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",14) DS EPITimeRateOfChangeOfYComponent 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",15) DS EPITimeRateOfChangeOfZComponent 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",16) DS EPITimeRateOfChangeLegalLimit1 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",17) DS EPIOperationModeFlag 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",18) DS EPIFieldCalculationSafetyFactor 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",19) DS EPILegalLimit1OfChangeValue 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",20) DS EPILegalLimit2OfChangeValue 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",21) DS EPIRiseTime 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",30) DS ArrayCoilADCOffset 16 PrivateTag +(0021,"SIEMENS MR VA0 RAW",31) DS ArrayCoilPreamplifierGain 16 PrivateTag +(0021,"SIEMENS MR VA0 RAW",50) LT SaturationType 1 PrivateTag +(0021,"SIEMENS MR VA0 RAW",51) DS SaturationNormalVector 3 PrivateTag +(0021,"SIEMENS MR VA0 RAW",52) DS SaturationPositionVector 3 PrivateTag +(0021,"SIEMENS MR VA0 RAW",53) DS SaturationThickness 6 PrivateTag +(0021,"SIEMENS MR VA0 RAW",54) DS SaturationWidth 6 PrivateTag +(0021,"SIEMENS MR VA0 RAW",55) DS SaturationDistance 6 PrivateTag + +(7fe3,"SIEMENS NUMARIS II",00) LT ImageGraphicsFormatCode 1 PrivateTag +(7fe3,"SIEMENS NUMARIS II",10) OB ImageGraphics 1 PrivateTag +(7fe3,"SIEMENS NUMARIS II",20) OB ImageGraphicsDummy 1 PrivateTag + +(0011,"SIEMENS RA GEN",20) SL FluoroTimer 1 PrivateTag +(0011,"SIEMENS RA GEN",25) SL PtopDoseAreaProduct 1 PrivateTag +(0011,"SIEMENS RA GEN",26) SL PtopTotalSkinDose 1 PrivateTag +(0011,"SIEMENS RA GEN",30) LT Unknown 1 PrivateTag +(0011,"SIEMENS RA GEN",35) LO PatientInitialPuckCounter 1 PrivateTag +(0011,"SIEMENS RA GEN",40) SS SPIDataObjectType 1 PrivateTag +(0019,"SIEMENS RA GEN",15) LO AcquiredPlane 1 PrivateTag +(0019,"SIEMENS RA GEN",1f) SS DefaultTableIsoCenterHeight 1 PrivateTag +(0019,"SIEMENS RA GEN",20) SL SceneFlag 1 PrivateTag +(0019,"SIEMENS RA GEN",22) SL RefPhotofileFlag 1 PrivateTag +(0019,"SIEMENS RA GEN",24) LO SceneName 1 PrivateTag +(0019,"SIEMENS RA GEN",26) SS AcquisitionIndex 1 PrivateTag +(0019,"SIEMENS RA GEN",28) SS MixedPulseMode 1 PrivateTag +(0019,"SIEMENS RA GEN",2a) SS NoOfPositions 1 PrivateTag +(0019,"SIEMENS RA GEN",2c) SS NoOfPhases 1 PrivateTag +(0019,"SIEMENS RA GEN",2e) SS FrameRateForPositions 1-n PrivateTag +(0019,"SIEMENS RA GEN",30) SS NoOfFramesForPositions 1-n PrivateTag +(0019,"SIEMENS RA GEN",32) SS SteppingDirection 1 PrivateTag +(0019,"SIEMENS RA GEN",34) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",36) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",38) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",3a) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",3c) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",3e) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",40) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",42) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",44) SS ImageTransferDelay 1 PrivateTag +(0019,"SIEMENS RA GEN",46) SL InversFlag 1 PrivateTag +(0019,"SIEMENS RA GEN",48) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",4a) US Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",4c) SS BlankingCircleDiameter 1 PrivateTag +(0019,"SIEMENS RA GEN",50) SL StandDataValid 1 PrivateTag +(0019,"SIEMENS RA GEN",52) SS TableTilt 1 PrivateTag +(0019,"SIEMENS RA GEN",54) SS TableAxisRotation 1 PrivateTag +(0019,"SIEMENS RA GEN",56) SS TableLongitudalPosition 1 PrivateTag +(0019,"SIEMENS RA GEN",58) SS TableSideOffset 1 PrivateTag +(0019,"SIEMENS RA GEN",5a) SS TableIsoCenterHeight 1 PrivateTag +(0019,"SIEMENS RA GEN",5c) UN Unknown 1 PrivateTag +(0019,"SIEMENS RA GEN",5e) SL CollimationDataValid 1 PrivateTag +(0019,"SIEMENS RA GEN",60) SL PeriSequenceNo 1 PrivateTag +(0019,"SIEMENS RA GEN",62) SL PeriTotalScenes 1 PrivateTag +(0019,"SIEMENS RA GEN",64) SL PeriOverlapTop 1 PrivateTag +(0019,"SIEMENS RA GEN",66) SL PeriOverlapBottom 1 PrivateTag +(0019,"SIEMENS RA GEN",68) SL RawImageNumber 1 PrivateTag +(0019,"SIEMENS RA GEN",6a) SL XRayDataValid 1 PrivateTag +(0019,"SIEMENS RA GEN",70) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",72) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",74) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",76) SL FillingAverageFactor 1 PrivateTag +(0019,"SIEMENS RA GEN",78) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",7a) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",7c) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",7e) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",80) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",82) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",84) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",86) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",88) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",8a) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",8c) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",8e) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",92) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",94) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",96) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",98) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",9a) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA GEN",9c) SL IntensifierLevelCalibrationFactor 1 PrivateTag +(0019,"SIEMENS RA GEN",9e) SL NativeReviewFlag 1 PrivateTag +(0019,"SIEMENS RA GEN",a2) SL SceneNumber 1 PrivateTag +(0019,"SIEMENS RA GEN",a4) SS AcquisitionMode 1 PrivateTag +(0019,"SIEMENS RA GEN",a5) SS AcquisitonFrameRate 1 PrivateTag +(0019,"SIEMENS RA GEN",a6) SL ECGFlag 1 PrivateTag +(0019,"SIEMENS RA GEN",a7) SL AdditionalSceneData 1 PrivateTag +(0019,"SIEMENS RA GEN",a8) SL FileCopyFlag 1 PrivateTag +(0019,"SIEMENS RA GEN",a9) SL PhlebovisionFlag 1 PrivateTag +(0019,"SIEMENS RA GEN",aa) SL Co2Flag 1 PrivateTag +(0019,"SIEMENS RA GEN",ab) SS MaxSpeed 1 PrivateTag +(0019,"SIEMENS RA GEN",ac) SS StepWidth 1 PrivateTag +(0019,"SIEMENS RA GEN",ad) SL DigitalAcquisitionZoom 1 PrivateTag +(0019,"SIEMENS RA GEN",ff) SS Internal 1-n PrivateTag +(0021,"SIEMENS RA GEN",15) SS ImagesInStudy 1 PrivateTag +(0021,"SIEMENS RA GEN",20) SS ScenesInStudy 1 PrivateTag +(0021,"SIEMENS RA GEN",25) SS ImagesInPhotofile 1 PrivateTag +(0021,"SIEMENS RA GEN",27) SS PlaneBImagesExist 1 PrivateTag +(0021,"SIEMENS RA GEN",28) SS NoOf2MBChunks 1 PrivateTag +(0021,"SIEMENS RA GEN",30) SS ImagesInAllScenes 1 PrivateTag +(0021,"SIEMENS RA GEN",40) SS ArchiveSWInternalVersion 1 PrivateTag + +(0011,"SIEMENS RA PLANE A",28) SL FluoroTimerA 1 PrivateTag +(0011,"SIEMENS RA PLANE A",29) SL FluoroSkinDoseA 1 PrivateTag +(0011,"SIEMENS RA PLANE A",2a) SL TotalSkinDoseA 1 PrivateTag +(0011,"SIEMENS RA PLANE A",2b) SL FluoroDoseAreaProductA 1 PrivateTag +(0011,"SIEMENS RA PLANE A",2c) SL TotalDoseAreaProductA 1 PrivateTag +(0019,"SIEMENS RA PLANE A",15) LT OfflineUID 1 PrivateTag +(0019,"SIEMENS RA PLANE A",18) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",19) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",1a) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",1b) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",1c) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",1d) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",1e) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",1f) SS Internal 1-n PrivateTag +(0019,"SIEMENS RA PLANE A",20) SS SystemCalibFactorPlaneA 1 PrivateTag +(0019,"SIEMENS RA PLANE A",22) SS XRayParameterSetNo 1 PrivateTag +(0019,"SIEMENS RA PLANE A",24) SS XRaySystem 1 PrivateTag +(0019,"SIEMENS RA PLANE A",26) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE A",28) SS AcquiredDisplayMode 1 PrivateTag +(0019,"SIEMENS RA PLANE A",2a) SS AcquisitionDelay 1 PrivateTag +(0019,"SIEMENS RA PLANE A",2c) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE A",2e) SS MaxFramesLimit 1 PrivateTag +(0019,"SIEMENS RA PLANE A",30) US MaximumFrameSizeNIU 1 PrivateTag +(0019,"SIEMENS RA PLANE A",32) SS SubtractedFilterType 1 PrivateTag +(0019,"SIEMENS RA PLANE A",34) SS FilterFactorNative 1 PrivateTag +(0019,"SIEMENS RA PLANE A",36) SS AnatomicBackgroundFactor 1 PrivateTag +(0019,"SIEMENS RA PLANE A",38) SS WindowUpperLimitNative 1 PrivateTag +(0019,"SIEMENS RA PLANE A",3a) SS WindowLowerLimitNative 1 PrivateTag +(0019,"SIEMENS RA PLANE A",3c) SS WindowBrightnessPhase1 1 PrivateTag +(0019,"SIEMENS RA PLANE A",3e) SS WindowBrightnessPhase2 1 PrivateTag +(0019,"SIEMENS RA PLANE A",40) SS WindowContrastPhase1 1 PrivateTag +(0019,"SIEMENS RA PLANE A",42) SS WindowContrastPhase2 1 PrivateTag +(0019,"SIEMENS RA PLANE A",44) SS FilterFactorSub 1 PrivateTag +(0019,"SIEMENS RA PLANE A",46) SS PeakOpacified 1 PrivateTag +(0019,"SIEMENS RA PLANE A",48) SL MaskFrame 1 PrivateTag +(0019,"SIEMENS RA PLANE A",4a) SL BIHFrame 1 PrivateTag +(0019,"SIEMENS RA PLANE A",4c) SS CentBeamAngulationCaudCran 1 PrivateTag +(0019,"SIEMENS RA PLANE A",4e) SS CentBeamAngulationLRAnterior 1 PrivateTag +(0019,"SIEMENS RA PLANE A",50) SS LongitudinalPosition 1 PrivateTag +(0019,"SIEMENS RA PLANE A",52) SS SideOffset 1 PrivateTag +(0019,"SIEMENS RA PLANE A",54) SS IsoCenterHeight 1 PrivateTag +(0019,"SIEMENS RA PLANE A",56) SS ImageTwist 1 PrivateTag +(0019,"SIEMENS RA PLANE A",58) SS SourceImageDistance 1 PrivateTag +(0019,"SIEMENS RA PLANE A",5a) SS MechanicalMagnificationFactor 1 PrivateTag +(0019,"SIEMENS RA PLANE A",5c) SL CalibrationFlag 1 PrivateTag +(0019,"SIEMENS RA PLANE A",5e) SL CalibrationAngleCranCaud 1 PrivateTag +(0019,"SIEMENS RA PLANE A",60) SL CalibrationAngleRAOLAO 1 PrivateTag +(0019,"SIEMENS RA PLANE A",62) SL CalibrationTableToFloorDist 1 PrivateTag +(0019,"SIEMENS RA PLANE A",64) SL CalibrationIsocenterToFloorDist 1 PrivateTag +(0019,"SIEMENS RA PLANE A",66) SL CalibrationIsocenterToSourceDist 1 PrivateTag +(0019,"SIEMENS RA PLANE A",68) SL CalibrationSourceToII 1 PrivateTag +(0019,"SIEMENS RA PLANE A",6a) SL CalibrationIIZoom 1 PrivateTag +(0019,"SIEMENS RA PLANE A",6c) SL CalibrationIIField 1 PrivateTag +(0019,"SIEMENS RA PLANE A",6e) SL CalibrationFactor 1 PrivateTag +(0019,"SIEMENS RA PLANE A",70) SL CalibrationObjectToImageDistance 1 PrivateTag +(0019,"SIEMENS RA PLANE A",72) SL CalibrationSystemFactor 1-n PrivateTag +(0019,"SIEMENS RA PLANE A",74) SL CalibrationSystemCorrection 1-n PrivateTag +(0019,"SIEMENS RA PLANE A",76) SL CalibrationSystemIIFormats 1-n PrivateTag +(0019,"SIEMENS RA PLANE A",78) SL CalibrationGantryDataValid 1 PrivateTag +(0019,"SIEMENS RA PLANE A",7a) SS CollimatorSquareBreadth 1 PrivateTag +(0019,"SIEMENS RA PLANE A",7c) SS CollimatorSquareHeight 1 PrivateTag +(0019,"SIEMENS RA PLANE A",7e) SS CollimatorSquareDiameter 1 PrivateTag +(0019,"SIEMENS RA PLANE A",80) SS CollimaterFingerTurnAngle 1 PrivateTag +(0019,"SIEMENS RA PLANE A",82) SS CollimaterFingerPosition 1 PrivateTag +(0019,"SIEMENS RA PLANE A",84) SS CollimaterDiaphragmTurnAngle 1 PrivateTag +(0019,"SIEMENS RA PLANE A",86) SS CollimaterDiaphragmPosition1 1 PrivateTag +(0019,"SIEMENS RA PLANE A",88) SS CollimaterDiaphragmPosition2 1 PrivateTag +(0019,"SIEMENS RA PLANE A",8a) SS CollimaterDiaphragmMode 1 PrivateTag +(0019,"SIEMENS RA PLANE A",8c) SS CollimaterBeamLimitBreadth 1 PrivateTag +(0019,"SIEMENS RA PLANE A",8e) SS CollimaterBeamLimitHeight 1 PrivateTag +(0019,"SIEMENS RA PLANE A",90) SS CollimaterBeamLimitDiameter 1 PrivateTag +(0019,"SIEMENS RA PLANE A",92) SS X-RayControlMOde 1 PrivateTag +(0019,"SIEMENS RA PLANE A",94) SS X-RaySystem 1 PrivateTag +(0019,"SIEMENS RA PLANE A",96) SS FocalSpot 1 PrivateTag +(0019,"SIEMENS RA PLANE A",98) SS ExposureControl 1 PrivateTag +(0019,"SIEMENS RA PLANE A",9a) SL XRayVoltage 1 PrivateTag +(0019,"SIEMENS RA PLANE A",9c) SL XRayCurrent 1 PrivateTag +(0019,"SIEMENS RA PLANE A",9e) SL XRayCurrentTimeProduct 1 PrivateTag +(0019,"SIEMENS RA PLANE A",a0) SL XRayPulseTime 1 PrivateTag +(0019,"SIEMENS RA PLANE A",a2) SL XRaySceneTimeFluoroClock 1 PrivateTag +(0019,"SIEMENS RA PLANE A",a4) SS MaximumPulseRate 1 PrivateTag +(0019,"SIEMENS RA PLANE A",a6) SS PulsesPerScene 1 PrivateTag +(0019,"SIEMENS RA PLANE A",a8) SL DoseAreaProductOfScene 1 PrivateTag +(0019,"SIEMENS RA PLANE A",aa) SS Dose 1 PrivateTag +(0019,"SIEMENS RA PLANE A",ac) SS DoseRate 1 PrivateTag +(0019,"SIEMENS RA PLANE A",ae) SL IIToCoverDistance 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b0) SS LastFramePhase1 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b1) SS FrameRatePhase1 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b2) SS LastFramePhase2 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b3) SS FrameRatePhase2 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b4) SS LastFramePhase3 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b5) SS FrameRatePhase3 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b6) SS LastFramePhase4 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b7) SS FrameRatePhase4 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b8) SS GammaOfNativeImage 1 PrivateTag +(0019,"SIEMENS RA PLANE A",b9) SS GammaOfTVSystem 1 PrivateTag +(0019,"SIEMENS RA PLANE A",bb) SL PixelshiftX 1 PrivateTag +(0019,"SIEMENS RA PLANE A",bc) SL PixelshiftY 1 PrivateTag +(0019,"SIEMENS RA PLANE A",bd) SL MaskAverageFactor 1 PrivateTag +(0019,"SIEMENS RA PLANE A",be) SL BlankingCircleFlag 1 PrivateTag +(0019,"SIEMENS RA PLANE A",bf) SL CircleRowStart 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c0) SL CircleRowEnd 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c1) SL CircleColumnStart 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c2) SL CircleColumnEnd 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c3) SL CircleDiameter 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c4) SL RectangularCollimaterFlag 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c5) SL RectangleRowStart 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c6) SL RectangleRowEnd 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c7) SL RectangleColumnStart 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c8) SL RectangleColumnEnd 1 PrivateTag +(0019,"SIEMENS RA PLANE A",c9) SL RectangleAngulation 1 PrivateTag +(0019,"SIEMENS RA PLANE A",ca) SL IrisCollimatorFlag 1 PrivateTag +(0019,"SIEMENS RA PLANE A",cb) SL IrisRowStart 1 PrivateTag +(0019,"SIEMENS RA PLANE A",cc) SL IrisRowEnd 1 PrivateTag +(0019,"SIEMENS RA PLANE A",cd) SL IrisColumnStart 1 PrivateTag +(0019,"SIEMENS RA PLANE A",ce) SL IrisColumnEnd 1 PrivateTag +(0019,"SIEMENS RA PLANE A",cf) SL IrisAngulation 1 PrivateTag +(0019,"SIEMENS RA PLANE A",d1) SS NumberOfFramesPlane 1 PrivateTag +(0019,"SIEMENS RA PLANE A",d2) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",d3) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",d4) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",d5) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",d6) SS Internal 1-n PrivateTag +(0019,"SIEMENS RA PLANE A",d7) SS Internal 1-n PrivateTag +(0019,"SIEMENS RA PLANE A",d8) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",d9) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",da) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",db) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",dc) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",dd) SL AnatomicBackground 1 PrivateTag +(0019,"SIEMENS RA PLANE A",de) SL AutoWindowBase 1-n PrivateTag +(0019,"SIEMENS RA PLANE A",df) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE A",e0) SL Internal 1 PrivateTag + +(0011,"SIEMENS RA PLANE B",28) SL FluoroTimerB 1 PrivateTag +(0011,"SIEMENS RA PLANE B",29) SL FluoroSkinDoseB 1 PrivateTag +(0011,"SIEMENS RA PLANE B",2a) SL TotalSkinDoseB 1 PrivateTag +(0011,"SIEMENS RA PLANE B",2b) SL FluoroDoseAreaProductB 1 PrivateTag +(0011,"SIEMENS RA PLANE B",2c) SL TotalDoseAreaProductB 1 PrivateTag +(0019,"SIEMENS RA PLANE B",18) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",19) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",1a) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",1b) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",1c) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",1d) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",1e) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",1f) SS Internal 1 PrivateTag +(0019,"SIEMENS RA PLANE B",20) SL SystemCalibFactorPlaneB 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",22) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",24) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",26) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",28) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",2a) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",2c) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",2e) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",30) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",32) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",34) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",36) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",38) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",3a) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",3c) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",3e) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",40) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",42) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",44) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",46) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",48) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",4a) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",4c) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",4e) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",50) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",52) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",54) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",56) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",58) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",5a) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",5c) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",5e) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",60) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",62) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",64) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",66) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",68) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",6a) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",6c) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",6e) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",70) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",72) UN Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",74) UN Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",76) UN Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",78) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",7a) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",7c) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",7e) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",80) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",82) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",84) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",86) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",88) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",8a) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",8c) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",8e) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",90) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",92) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",94) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",96) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",98) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",9a) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",9c) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",9e) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",a0) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",a2) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",a4) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",a6) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",a8) US Unknown 1-n PrivateTag +(0019,"SIEMENS RA PLANE B",aa) US Unknown 1 PrivateTag +(0019,"SIEMENS RA PLANE B",ac) US Unknown 1 PrivateTag + +(0011,"SIEMENS RIS",10) LT PatientUID 1 PrivateTag +(0011,"SIEMENS RIS",11) LT PatientID 1 PrivateTag +(0011,"SIEMENS RIS",20) DA PatientRegistrationDate 1 PrivateTag +(0011,"SIEMENS RIS",21) TM PatientRegistrationTime 1 PrivateTag +(0011,"SIEMENS RIS",30) LT PatientnameRIS 1 PrivateTag +(0011,"SIEMENS RIS",31) LT PatientprenameRIS 1 PrivateTag +(0011,"SIEMENS RIS",40) LT PatientHospitalStatus 1 PrivateTag +(0011,"SIEMENS RIS",41) LT MedicalAlerts 1 PrivateTag +(0011,"SIEMENS RIS",42) LT ContrastAllergies 1 PrivateTag +(0031,"SIEMENS RIS",10) LT RequestUID 1 PrivateTag +(0031,"SIEMENS RIS",45) LT RequestingPhysician 1 PrivateTag +(0031,"SIEMENS RIS",50) LT RequestedPhysician 1 PrivateTag +(0033,"SIEMENS RIS",10) LT PatientStudyUID 1 PrivateTag + +(0021,"SIEMENS SMS-AX ACQ 1.0",00) US AcquisitionType 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",01) US AcquisitionMode 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",02) US FootswitchIndex 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",03) US AcquisitionRoom 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",04) SL CurrentTimeProduct 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",05) SL Dose 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",06) SL SkinDosePercent 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",07) SL SkinDoseAccumulation 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",08) SL SkinDoseRate 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",0A) UL CopperFilter 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",0B) US MeasuringField 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",0C) SS PostBlankingCircle 3 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",0D) SS DynaAngles 2-2n PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",0E) SS TotalSteps 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",0F) SL DynaXRayInfo 3-3n PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",10) US ModalityLUTInputGamma 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",11) US ModalityLUTOutputGamma 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",12) OB SH_STPAR 1-n PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",13) US AcquisitionZoom 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",14) SS DynaAngulationStepWidth 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",15) US Harmonization 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",16) US DRSingleFlag 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",17) SL SourceToIsocenter 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",18) US PressureData 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",19) SL ECGIndexArray 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",1A) US FDFlag 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",1B) OB SH_ZOOM 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",1C) OB SH_COLPAR 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",1D) US K_Factor 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",1E) US EVE 8 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",1F) SL TotalSceneTime 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",20) US RestoreFlag 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",21) US StandMovementFlag 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",22) US FDRows 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",23) US FDColumns 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",24) US TableMovementFlag 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",25) LO OriginalOrganProgramName 1 PrivateTag +(0021,"SIEMENS SMS-AX ACQ 1.0",26) DS CrispyXPIFilter 1 PrivateTag + +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",00) US ViewNative 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",01) US OriginalSeriesNumber 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",02) US OriginalImageNumber 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",03) US WinCenter 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",04) US WinWidth 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",05) US WinBrightness 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",06) US WinContrast 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",07) US OriginalFrameNumber 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",08) US OriginalMaskFrameNumber 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",09) US Opac 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",0A) US OriginalNumberOfFrames 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",0B) DS OriginalSceneDuration 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",0C) LO IdentifierLOID 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",0D) SS OriginalSceneVFRInfo 1-n PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",0E) SS OriginalFrameECGPosition 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",0F) SS OriginalECG1stFrameOffset_retired 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",10) SS ZoomFlag 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",11) US Flex 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",12) US NumberOfMaskFrames 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",13) US NumberOfFillFrames 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",14) US SeriesNumber 1 PrivateTag +(0025,"SIEMENS SMS-AX ORIGINAL IMAGE INFO 1.0",15) IS ImageNumber 1 PrivateTag + +(0023,"SIEMENS SMS-AX QUANT 1.0",00) DS HorizontalCalibrationPixelSize 2 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",01) DS VerticalCalibrationPixelSize 2 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",02) LO CalibrationObject 1 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",03) DS CalibrationObjectSize 1 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",04) LO CalibrationMethod 1 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",05) ST Filename 1 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",06) IS FrameNumber 1 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",07) IS CalibrationFactorMultiplicity 2 PrivateTag +(0023,"SIEMENS SMS-AX QUANT 1.0",08) IS CalibrationTODValue 1 PrivateTag + +(0019,"SIEMENS SMS-AX VIEW 1.0",00) US ReviewMode 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",01) US AnatomicalBackgroundPercent 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",02) US NumberOfPhases 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",03) US ApplyAnatomicalBackground 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",04) SS PixelShiftArray 4-4n PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",05) US Brightness 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",06) US Contrast 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",07) US Enabled 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",08) US NativeEdgeEnhancementPercentGain 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",09) SS NativeEdgeEnhancementLUTIndex 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",0A) SS NativeEdgeEnhancementKernelSize 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",0B) US SubtrEdgeEnhancementPercentGain 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",0C) SS SubtrEdgeEnhancementLUTIndex 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",0D) SS SubtrEdgeEnhancementKernelSize 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",0E) US FadePercent 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",0F) US FlippedBeforeLateralityApplied 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",10) US ApplyFade 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",12) US Zoom 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",13) SS PanX 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",14) SS PanY 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",15) SS NativeEdgeEnhancementAdvPercGain 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",16) SS SubtrEdgeEnhancementAdvPercGain 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",17) US InvertFlag 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",1A) OB Quant1KOverlay 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",1B) US OriginalResolution 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",1C) DS AutoWindowCenter 1 PrivateTag +(0019,"SIEMENS SMS-AX VIEW 1.0",1D) DS AutoWindowWidth 1 PrivateTag + +(0009,"SIENET",01) US SIENETCommandField 1 PrivateTag +(0009,"SIENET",14) LT ReceiverPLA 1 PrivateTag +(0009,"SIENET",16) US TransferPriority 1 PrivateTag +(0009,"SIENET",29) LT ActualUser 1 PrivateTag +(0095,"SIENET",01) LT ExaminationFolderID 1 PrivateTag +(0095,"SIENET",04) UL FolderReportedStatus 1 PrivateTag +(0095,"SIENET",05) LT FolderReportingRadiologist 1 PrivateTag +(0095,"SIENET",07) LT SIENETISAPLA 1 PrivateTag +(0099,"SIENET",02) UL DataObjectAttributes 1 PrivateTag + +(0009,"SPI RELEASE 1",10) LT Comments 1 PrivateTag +(0009,"SPI RELEASE 1",15) LO SPIImageUID 1 PrivateTag +(0009,"SPI RELEASE 1",40) US DataObjectType 1 PrivateTag +(0009,"SPI RELEASE 1",41) LO DataObjectSubtype 1 PrivateTag +(0011,"SPI RELEASE 1",10) LO Organ 1 PrivateTag +(0011,"SPI RELEASE 1",15) LO AllergyIndication 1 PrivateTag +(0011,"SPI RELEASE 1",20) LO Pregnancy 1 PrivateTag +(0029,"SPI RELEASE 1",60) LT CompressionAlgorithm 1 PrivateTag + +(0009,"SPI Release 1",10) LT Comments 1 PrivateTag +(0009,"SPI Release 1",15) LO SPIImageUID 1 PrivateTag +(0009,"SPI Release 1",40) US DataObjectType 1 PrivateTag +(0009,"SPI Release 1",41) LO DataObjectSubtype 1 PrivateTag +(0011,"SPI Release 1",10) LO Organ 1 PrivateTag +(0011,"SPI Release 1",15) LO AllergyIndication 1 PrivateTag +(0011,"SPI Release 1",20) LO Pregnancy 1 PrivateTag +(0029,"SPI Release 1",60) LT CompressionAlgorithm 1 PrivateTag + +(0009,"SPI",10) LO Comments 1 PrivateTag +(0009,"SPI",15) LO SPIImageUID 1 PrivateTag +(0009,"SPI",40) US DataObjectType 1 PrivateTag +(0009,"SPI",41) LT DataObjectSubtype 1 PrivateTag +(0011,"SPI",10) LT Organ 1 PrivateTag +(0011,"SPI",15) LT AllergyIndication 1 PrivateTag +(0011,"SPI",20) LT Pregnancy 1 PrivateTag +(0029,"SPI",60) LT CompressionAlgorithm 1 PrivateTag + +(0011,"SPI RELEASE 1",10) LO Organ 1 PrivateTag +(0011,"SPI RELEASE 1",15) LO AllergyIndication 1 PrivateTag +(0011,"SPI RELEASE 1",20) LO Pregnancy 1 PrivateTag + +(0009,"SPI-P Release 1",00) LT DataObjectRecognitionCode 1 PrivateTag +(0009,"SPI-P Release 1",04) LO ImageDataConsistence 1 PrivateTag +(0009,"SPI-P Release 1",08) US Unknown 1 PrivateTag +(0009,"SPI-P Release 1",12) LO Unknown 1 PrivateTag +(0009,"SPI-P Release 1",15) LO UniqueIdentifier 1 PrivateTag +(0009,"SPI-P Release 1",16) LO Unknown 1 PrivateTag +(0009,"SPI-P Release 1",18) LO Unknown 1 PrivateTag +(0009,"SPI-P Release 1",21) LT Unknown 1 PrivateTag +(0009,"SPI-P Release 1",31) LT PACSUniqueIdentifier 1 PrivateTag +(0009,"SPI-P Release 1",34) LT ClusterUniqueIdentifier 1 PrivateTag +(0009,"SPI-P Release 1",38) LT SystemUniqueIdentifier 1 PrivateTag +(0009,"SPI-P Release 1",39) LT Unknown 1 PrivateTag +(0009,"SPI-P Release 1",51) LT StudyUniqueIdentifier 1 PrivateTag +(0009,"SPI-P Release 1",61) LT SeriesUniqueIdentifier 1 PrivateTag +(0009,"SPI-P Release 1",91) LT Unknown 1 PrivateTag +(0009,"SPI-P Release 1",f2) LT Unknown 1 PrivateTag +(0009,"SPI-P Release 1",f3) UN Unknown 1 PrivateTag +(0009,"SPI-P Release 1",f4) LT Unknown 1 PrivateTag +(0009,"SPI-P Release 1",f5) UN Unknown 1 PrivateTag +(0009,"SPI-P Release 1",f7) LT Unknown 1 PrivateTag +(0011,"SPI-P Release 1",10) LT PatientEntryID 1 PrivateTag +(0011,"SPI-P Release 1",21) UN Unknown 1 PrivateTag +(0011,"SPI-P Release 1",22) UN Unknown 1 PrivateTag +(0011,"SPI-P Release 1",31) UN Unknown 1 PrivateTag +(0011,"SPI-P Release 1",32) UN Unknown 1 PrivateTag +(0019,"SPI-P Release 1",00) UN Unknown 1 PrivateTag +(0019,"SPI-P Release 1",01) UN Unknown 1 PrivateTag +(0019,"SPI-P Release 1",02) UN Unknown 1 PrivateTag +(0019,"SPI-P Release 1",10) US MainsFrequency 1 PrivateTag +(0019,"SPI-P Release 1",25) LT OriginalPixelDataQuality 1-n PrivateTag +(0019,"SPI-P Release 1",30) US ECGTriggering 1 PrivateTag +(0019,"SPI-P Release 1",31) UN ECG1Offset 1 PrivateTag +(0019,"SPI-P Release 1",32) UN ECG2Offset1 1 PrivateTag +(0019,"SPI-P Release 1",33) UN ECG2Offset2 1 PrivateTag +(0019,"SPI-P Release 1",50) US VideoScanMode 1 PrivateTag +(0019,"SPI-P Release 1",51) US VideoLineRate 1 PrivateTag +(0019,"SPI-P Release 1",60) US XrayTechnique 1 PrivateTag +(0019,"SPI-P Release 1",61) DS ImageIdentifierFromat 1 PrivateTag +(0019,"SPI-P Release 1",62) US IrisDiaphragm 1 PrivateTag +(0019,"SPI-P Release 1",63) CS Filter 1 PrivateTag +(0019,"SPI-P Release 1",64) CS CineParallel 1 PrivateTag +(0019,"SPI-P Release 1",65) CS CineMaster 1 PrivateTag +(0019,"SPI-P Release 1",70) US ExposureChannel 1 PrivateTag +(0019,"SPI-P Release 1",71) UN ExposureChannelFirstImage 1 PrivateTag +(0019,"SPI-P Release 1",72) US ProcessingChannel 1 PrivateTag +(0019,"SPI-P Release 1",80) DS AcquisitionDelay 1 PrivateTag +(0019,"SPI-P Release 1",81) UN RelativeImageTime 1 PrivateTag +(0019,"SPI-P Release 1",90) CS VideoWhiteCompression 1 PrivateTag +(0019,"SPI-P Release 1",a0) US Angulation 1 PrivateTag +(0019,"SPI-P Release 1",a1) US Rotation 1 PrivateTag +(0021,"SPI-P Release 1",12) LT SeriesUniqueIdentifier 1 PrivateTag +(0021,"SPI-P Release 1",14) LT Unknown 1 PrivateTag +(0029,"SPI-P Release 1",00) DS Unknown 4 PrivateTag +(0029,"SPI-P Release 1",20) DS PixelAspectRatio 1 PrivateTag +(0029,"SPI-P Release 1",25) LO ProcessedPixelDataQuality 1-n PrivateTag +(0029,"SPI-P Release 1",30) LT Unknown 1 PrivateTag +(0029,"SPI-P Release 1",38) US Unknown 1 PrivateTag +(0029,"SPI-P Release 1",60) LT Unknown 1 PrivateTag +(0029,"SPI-P Release 1",61) LT Unknown 1 PrivateTag +(0029,"SPI-P Release 1",67) LT Unknown 1 PrivateTag +(0029,"SPI-P Release 1",70) LT WindowID 1 PrivateTag +(0029,"SPI-P Release 1",71) CS VideoInvertSubtracted 1 PrivateTag +(0029,"SPI-P Release 1",72) CS VideoInvertNonsubtracted 1 PrivateTag +(0029,"SPI-P Release 1",77) CS WindowSelectStatus 1 PrivateTag +(0029,"SPI-P Release 1",78) LT ECGDisplayPrintingID 1 PrivateTag +(0029,"SPI-P Release 1",79) CS ECGDisplayPrinting 1 PrivateTag +(0029,"SPI-P Release 1",7e) CS ECGDisplayPrintingEnableStatus 1 PrivateTag +(0029,"SPI-P Release 1",7f) CS ECGDisplayPrintingSelectStatus 1 PrivateTag +(0029,"SPI-P Release 1",80) LT PhysiologicalDisplayID 1 PrivateTag +(0029,"SPI-P Release 1",81) US PreferredPhysiologicalChannelDisplay 1 PrivateTag +(0029,"SPI-P Release 1",8e) CS PhysiologicalDisplayEnableStatus 1 PrivateTag +(0029,"SPI-P Release 1",8f) CS PhysiologicalDisplaySelectStatus 1 PrivateTag +(0029,"SPI-P Release 1",c0) LT FunctionalShutterID 1 PrivateTag +(0029,"SPI-P Release 1",c1) US FieldOfShutter 1 PrivateTag +(0029,"SPI-P Release 1",c5) LT FieldOfShutterRectangle 1 PrivateTag +(0029,"SPI-P Release 1",ce) CS ShutterEnableStatus 1 PrivateTag +(0029,"SPI-P Release 1",cf) CS ShutterSelectStatus 1 PrivateTag +(7FE1,"SPI-P Release 1",10) ox PixelData 1 PrivateTag + +(0009,"SPI-P Release 1;1",c0) LT Unknown 1 PrivateTag +(0009,"SPI-P Release 1;1",c1) LT Unknown 1 PrivateTag +(0019,"SPI-P Release 1;1",00) UN PhysiologicalDataType 1 PrivateTag +(0019,"SPI-P Release 1;1",01) UN PhysiologicalDataChannelAndKind 1 PrivateTag +(0019,"SPI-P Release 1;1",02) US SampleBitsAllocated 1 PrivateTag +(0019,"SPI-P Release 1;1",03) US SampleBitsStored 1 PrivateTag +(0019,"SPI-P Release 1;1",04) US SampleHighBit 1 PrivateTag +(0019,"SPI-P Release 1;1",05) US SampleRepresentation 1 PrivateTag +(0019,"SPI-P Release 1;1",06) UN SmallestSampleValue 1 PrivateTag +(0019,"SPI-P Release 1;1",07) UN LargestSampleValue 1 PrivateTag +(0019,"SPI-P Release 1;1",08) UN NumberOfSamples 1 PrivateTag +(0019,"SPI-P Release 1;1",09) UN SampleData 1 PrivateTag +(0019,"SPI-P Release 1;1",0a) UN SampleRate 1 PrivateTag +(0019,"SPI-P Release 1;1",10) UN PhysiologicalDataType2 1 PrivateTag +(0019,"SPI-P Release 1;1",11) UN PhysiologicalDataChannelAndKind2 1 PrivateTag +(0019,"SPI-P Release 1;1",12) US SampleBitsAllocated2 1 PrivateTag +(0019,"SPI-P Release 1;1",13) US SampleBitsStored2 1 PrivateTag +(0019,"SPI-P Release 1;1",14) US SampleHighBit2 1 PrivateTag +(0019,"SPI-P Release 1;1",15) US SampleRepresentation2 1 PrivateTag +(0019,"SPI-P Release 1;1",16) UN SmallestSampleValue2 1 PrivateTag +(0019,"SPI-P Release 1;1",17) UN LargestSampleValue2 1 PrivateTag +(0019,"SPI-P Release 1;1",18) UN NumberOfSamples2 1 PrivateTag +(0019,"SPI-P Release 1;1",19) UN SampleData2 1 PrivateTag +(0019,"SPI-P Release 1;1",1a) UN SampleRate2 1 PrivateTag +(0029,"SPI-P Release 1;1",00) LT ZoomID 1 PrivateTag +(0029,"SPI-P Release 1;1",01) DS ZoomRectangle 1-n PrivateTag +(0029,"SPI-P Release 1;1",03) DS ZoomFactor 1 PrivateTag +(0029,"SPI-P Release 1;1",04) US ZoomFunction 1 PrivateTag +(0029,"SPI-P Release 1;1",0e) CS ZoomEnableStatus 1 PrivateTag +(0029,"SPI-P Release 1;1",0f) CS ZoomSelectStatus 1 PrivateTag +(0029,"SPI-P Release 1;1",40) LT MagnifyingGlassID 1 PrivateTag +(0029,"SPI-P Release 1;1",41) DS MagnifyingGlassRectangle 1-n PrivateTag +(0029,"SPI-P Release 1;1",43) DS MagnifyingGlassFactor 1 PrivateTag +(0029,"SPI-P Release 1;1",44) US MagnifyingGlassFunction 1 PrivateTag +(0029,"SPI-P Release 1;1",4e) CS MagnifyingGlassEnableStatus 1 PrivateTag +(0029,"SPI-P Release 1;1",4f) CS MagnifyingGlassSelectStatus 1 PrivateTag + +(0029,"SPI-P Release 1;2",00) LT SubtractionMaskID 1 PrivateTag +(0029,"SPI-P Release 1;2",04) UN MaskingFunction 1 PrivateTag +(0029,"SPI-P Release 1;2",0c) UN ProprietaryMaskingParameters 1 PrivateTag +(0029,"SPI-P Release 1;2",1e) CS SubtractionMaskEnableStatus 1 PrivateTag +(0029,"SPI-P Release 1;2",1f) CS SubtractionMaskSelectStatus 1 PrivateTag +(0029,"SPI-P Release 1;3",00) LT ImageEnhancementID 1 PrivateTag +(0029,"SPI-P Release 1;3",01) LT ImageEnhancement 1 PrivateTag +(0029,"SPI-P Release 1;3",02) LT ConvolutionID 1 PrivateTag +(0029,"SPI-P Release 1;3",03) LT ConvolutionType 1 PrivateTag +(0029,"SPI-P Release 1;3",04) LT ConvolutionKernelSizeID 1 PrivateTag +(0029,"SPI-P Release 1;3",05) US ConvolutionKernelSize 2 PrivateTag +(0029,"SPI-P Release 1;3",06) US ConvolutionKernel 1-n PrivateTag +(0029,"SPI-P Release 1;3",0c) DS EnhancementGain 1 PrivateTag +(0029,"SPI-P Release 1;3",1e) CS ImageEnhancementEnableStatus 1 PrivateTag +(0029,"SPI-P Release 1;3",1f) CS ImageEnhancementSelectStatus 1 PrivateTag + +(0011,"SPI-P Release 2;1",18) LT Unknown 1 PrivateTag +(0023,"SPI-P Release 2;1",0d) UI Unknown 1 PrivateTag +(0023,"SPI-P Release 2;1",0e) UI Unknown 1 PrivateTag + +(0009,"SPI-P-GV-CT Release 1",00) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",10) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",20) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",30) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",40) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",50) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",60) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",70) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",75) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",80) LO Unknown 1 PrivateTag +(0009,"SPI-P-GV-CT Release 1",90) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",08) IS Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",09) IS Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",0a) IS Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",10) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",20) TM Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",50) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",60) DS Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",61) US Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",63) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",64) US Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",65) IS Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",70) LT Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",80) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",81) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",90) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",a0) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",a1) US Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",a2) US Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",a3) US Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",b0) LO Unknown 1 PrivateTag +(0019,"SPI-P-GV-CT Release 1",b1) LO Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",20) LO Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",30) DS Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",40) LO Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",50) LO Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",60) DS Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",70) DS Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",80) DS Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",90) DS Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",a0) US Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",a1) DS Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",a2) DS Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",a3) LT Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",a4) LT Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",b0) LO Unknown 1 PrivateTag +(0021,"SPI-P-GV-CT Release 1",c0) LO Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",10) LO Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",30) UL Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",31) UL Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",32) UL Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",33) UL Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",80) LO Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",90) LO Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",d0) IS Unknown 1 PrivateTag +(0029,"SPI-P-GV-CT Release 1",d1) IS Unknown 1 PrivateTag + +(0019,"SPI-P-PCR Release 2",30) US Unknown 1 PrivateTag + +(0021,"SPI-P-Private-CWS Release 1",00) LT WindowOfImagesID 1 PrivateTag +(0021,"SPI-P-Private-CWS Release 1",01) CS WindowOfImagesType 1 PrivateTag +(0021,"SPI-P-Private-CWS Release 1",02) IS WindowOfImagesScope 1-n PrivateTag + +(0019,"SPI-P-Private-DCI Release 1",10) UN ECGTimeMapDataBitsAllocated 1 PrivateTag +(0019,"SPI-P-Private-DCI Release 1",11) UN ECGTimeMapDataBitsStored 1 PrivateTag +(0019,"SPI-P-Private-DCI Release 1",12) UN ECGTimeMapDataHighBit 1 PrivateTag +(0019,"SPI-P-Private-DCI Release 1",13) UN ECGTimeMapDataRepresentation 1 PrivateTag +(0019,"SPI-P-Private-DCI Release 1",14) UN ECGTimeMapDataSmallestDataValue 1 PrivateTag +(0019,"SPI-P-Private-DCI Release 1",15) UN ECGTimeMapDataLargestDataValue 1 PrivateTag +(0019,"SPI-P-Private-DCI Release 1",16) UN ECGTimeMapDataNumberOfDataValues 1 PrivateTag +(0019,"SPI-P-Private-DCI Release 1",17) UN ECGTimeMapData 1 PrivateTag + +(0021,"SPI-P-Private_CDS Release 1",40) IS Unknown 1 PrivateTag +(0029,"SPI-P-Private_CDS Release 1",00) UN Unknown 1 PrivateTag + +(0019,"SPI-P-Private_ICS Release 1",30) DS Unknown 1 PrivateTag +(0019,"SPI-P-Private_ICS Release 1",31) LO Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",08) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",0f) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",10) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",1b) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",1c) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",21) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",43) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",44) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",4C) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",67) LO Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",68) US Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",6A) LO Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1",6B) US Unknown 1 PrivateTag + +(0029,"SPI-P-Private_ICS Release 1;1",00) SL Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;1",05) FL Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;1",06) FL Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;1",20) FL Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;1",21) FL Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;1",CD) SQ Unknown 1 PrivateTag + +(0029,"SPI-P-Private_ICS Release 1;2",00) FD Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;2",01) FD Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;2",02) FD Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;2",03) SL Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;2",04) SL Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;2",05) SL Unknown 1 PrivateTag + +(0029,"SPI-P-Private_ICS Release 1;3",C0) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;3",C1) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;3",C2) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;3",C3) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;3",C4) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;3",C5) SQ Unknown 1 PrivateTag + +(0029,"SPI-P-Private_ICS Release 1;4",02) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;4",9A) SQ Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;4",E0) SQ Unknown 1 PrivateTag + +(0029,"SPI-P-Private_ICS Release 1;5",50) CS Unknown 1 PrivateTag +(0029,"SPI-P-Private_ICS Release 1;5",55) CS Unknown 1 PrivateTag + +(0019,"SPI-P-XSB-DCI Release 1",10) LT VideoBeamBoost 1 PrivateTag +(0019,"SPI-P-XSB-DCI Release 1",11) US ChannelGeneratingVideoSync 1 PrivateTag +(0019,"SPI-P-XSB-DCI Release 1",12) US VideoGain 1 PrivateTag +(0019,"SPI-P-XSB-DCI Release 1",13) US VideoOffset 1 PrivateTag +(0019,"SPI-P-XSB-DCI Release 1",20) DS RTDDataCompressionFactor 1 PrivateTag + +(0029,"Silhouette Annot V1.0",11) IS AnnotationName 1 PrivateTag +(0029,"Silhouette Annot V1.0",12) LT AnnotationFont 1 PrivateTag +(0029,"Silhouette Annot V1.0",13) LT AnnotationTextForegroundColor 1 PrivateTag +(0029,"Silhouette Annot V1.0",14) LT AnnotationTextBackgroundColor 1 PrivateTag +(0029,"Silhouette Annot V1.0",15) UL AnnotationTextBackingMode 1 PrivateTag +(0029,"Silhouette Annot V1.0",16) UL AnnotationTextJustification 1 PrivateTag +(0029,"Silhouette Annot V1.0",17) UL AnnotationTextLocation 1 PrivateTag +(0029,"Silhouette Annot V1.0",18) LT AnnotationTextString 1 PrivateTag +(0029,"Silhouette Annot V1.0",19) UL AnnotationTextAttachMode 1 PrivateTag +(0029,"Silhouette Annot V1.0",20) UL AnnotationTextCursorMode 1 PrivateTag +(0029,"Silhouette Annot V1.0",21) UL AnnotationTextShadowOffsetX 1 PrivateTag +(0029,"Silhouette Annot V1.0",22) UL AnnotationTextShadowOffsetY 1 PrivateTag +(0029,"Silhouette Annot V1.0",23) LT AnnotationLineColor 1 PrivateTag +(0029,"Silhouette Annot V1.0",24) UL AnnotationLineThickness 1 PrivateTag +(0029,"Silhouette Annot V1.0",25) UL AnnotationLineType 1 PrivateTag +(0029,"Silhouette Annot V1.0",26) UL AnnotationLineStyle 1 PrivateTag +(0029,"Silhouette Annot V1.0",27) UL AnnotationLineDashLength 1 PrivateTag +(0029,"Silhouette Annot V1.0",28) UL AnnotationLineAttachMode 1 PrivateTag +(0029,"Silhouette Annot V1.0",29) UL AnnotationLinePointCount 1 PrivateTag +(0029,"Silhouette Annot V1.0",30) FD AnnotationLinePoints 1 PrivateTag +(0029,"Silhouette Annot V1.0",31) UL AnnotationLineControlSize 1 PrivateTag +(0029,"Silhouette Annot V1.0",32) LT AnnotationMarkerColor 1 PrivateTag +(0029,"Silhouette Annot V1.0",33) UL AnnotationMarkerType 1 PrivateTag +(0029,"Silhouette Annot V1.0",34) UL AnnotationMarkerSize 1 PrivateTag +(0029,"Silhouette Annot V1.0",35) FD AnnotationMarkerLocation 1 PrivateTag +(0029,"Silhouette Annot V1.0",36) UL AnnotationMarkerAttachMode 1 PrivateTag +(0029,"Silhouette Annot V1.0",37) LT AnnotationGeomColor 1 PrivateTag +(0029,"Silhouette Annot V1.0",38) UL AnnotationGeomThickness 1 PrivateTag +(0029,"Silhouette Annot V1.0",39) UL AnnotationGeomLineStyle 1 PrivateTag +(0029,"Silhouette Annot V1.0",40) UL AnnotationGeomDashLength 1 PrivateTag +(0029,"Silhouette Annot V1.0",41) UL AnnotationGeomFillPattern 1 PrivateTag +(0029,"Silhouette Annot V1.0",42) UL AnnotationInteractivity 1 PrivateTag +(0029,"Silhouette Annot V1.0",43) FD AnnotationArrowLength 1 PrivateTag +(0029,"Silhouette Annot V1.0",44) FD AnnotationArrowAngle 1 PrivateTag +(0029,"Silhouette Annot V1.0",45) UL AnnotationDontSave 1 PrivateTag + +(0029,"Silhouette Graphics Export V1.0",00) UI Unknown 1 PrivateTag + +(0029,"Silhouette Line V1.0",11) IS LineName 1 PrivateTag +(0029,"Silhouette Line V1.0",12) LT LineNameFont 1 PrivateTag +(0029,"Silhouette Line V1.0",13) UL LineNameDisplay 1 PrivateTag +(0029,"Silhouette Line V1.0",14) LT LineNormalColor 1 PrivateTag +(0029,"Silhouette Line V1.0",15) UL LineType 1 PrivateTag +(0029,"Silhouette Line V1.0",16) UL LineThickness 1 PrivateTag +(0029,"Silhouette Line V1.0",17) UL LineStyle 1 PrivateTag +(0029,"Silhouette Line V1.0",18) UL LineDashLength 1 PrivateTag +(0029,"Silhouette Line V1.0",19) UL LineInteractivity 1 PrivateTag +(0029,"Silhouette Line V1.0",20) LT LineMeasurementColor 1 PrivateTag +(0029,"Silhouette Line V1.0",21) LT LineMeasurementFont 1 PrivateTag +(0029,"Silhouette Line V1.0",22) UL LineMeasurementDashLength 1 PrivateTag +(0029,"Silhouette Line V1.0",23) UL LinePointSpace 1 PrivateTag +(0029,"Silhouette Line V1.0",24) FD LinePoints 1 PrivateTag +(0029,"Silhouette Line V1.0",25) UL LineControlPointSize 1 PrivateTag +(0029,"Silhouette Line V1.0",26) UL LineControlPointSpace 1 PrivateTag +(0029,"Silhouette Line V1.0",27) FD LineControlPoints 1 PrivateTag +(0029,"Silhouette Line V1.0",28) LT LineLabel 1 PrivateTag +(0029,"Silhouette Line V1.0",29) UL LineDontSave 1 PrivateTag + +(0029,"Silhouette ROI V1.0",11) IS ROIName 1 PrivateTag +(0029,"Silhouette ROI V1.0",12) LT ROINameFont 1 PrivateTag +(0029,"Silhouette ROI V1.0",13) LT ROINormalColor 1 PrivateTag +(0029,"Silhouette ROI V1.0",14) UL ROIFillPattern 1 PrivateTag +(0029,"Silhouette ROI V1.0",15) UL ROIBpSeg 1 PrivateTag +(0029,"Silhouette ROI V1.0",16) UN ROIBpSegPairs 1 PrivateTag +(0029,"Silhouette ROI V1.0",17) UL ROISeedSpace 1 PrivateTag +(0029,"Silhouette ROI V1.0",18) UN ROISeeds 1 PrivateTag +(0029,"Silhouette ROI V1.0",19) UL ROILineThickness 1 PrivateTag +(0029,"Silhouette ROI V1.0",20) UL ROILineStyle 1 PrivateTag +(0029,"Silhouette ROI V1.0",21) UL ROILineDashLength 1 PrivateTag +(0029,"Silhouette ROI V1.0",22) UL ROIInteractivity 1 PrivateTag +(0029,"Silhouette ROI V1.0",23) UL ROINamePosition 1 PrivateTag +(0029,"Silhouette ROI V1.0",24) UL ROINameDisplay 1 PrivateTag +(0029,"Silhouette ROI V1.0",25) LT ROILabel 1 PrivateTag +(0029,"Silhouette ROI V1.0",26) UL ROIShape 1 PrivateTag +(0029,"Silhouette ROI V1.0",27) FD ROIShapeTilt 1 PrivateTag +(0029,"Silhouette ROI V1.0",28) UL ROIShapePointsCount 1 PrivateTag +(0029,"Silhouette ROI V1.0",29) UL ROIShapePointsSpace 1 PrivateTag +(0029,"Silhouette ROI V1.0",30) FD ROIShapePoints 1 PrivateTag +(0029,"Silhouette ROI V1.0",31) UL ROIShapeControlPointsCount 1 PrivateTag +(0029,"Silhouette ROI V1.0",32) UL ROIShapeControlPointsSpace 1 PrivateTag +(0029,"Silhouette ROI V1.0",33) FD ROIShapeControlPoints 1 PrivateTag +(0029,"Silhouette ROI V1.0",34) UL ROIDontSave 1 PrivateTag + +(0029,"Silhouette Sequence Ids V1.0",41) SQ Unknown 1 PrivateTag +(0029,"Silhouette Sequence Ids V1.0",42) SQ Unknown 1 PrivateTag +(0029,"Silhouette Sequence Ids V1.0",43) SQ Unknown 1 PrivateTag + +(0029,"Silhouette V1.0",13) UL Unknown 1 PrivateTag +(0029,"Silhouette V1.0",14) UL Unknown 1 PrivateTag +(0029,"Silhouette V1.0",17) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",18) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",19) UL Unknown 1 PrivateTag +(0029,"Silhouette V1.0",1a) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",1b) UL Unknown 1 PrivateTag +(0029,"Silhouette V1.0",1c) UL Unknown 1 PrivateTag +(0029,"Silhouette V1.0",1d) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",1e) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",21) US Unknown 1 PrivateTag +(0029,"Silhouette V1.0",22) US Unknown 1 PrivateTag +(0029,"Silhouette V1.0",23) US Unknown 1 PrivateTag +(0029,"Silhouette V1.0",24) US Unknown 1 PrivateTag +(0029,"Silhouette V1.0",25) US Unknown 1 PrivateTag +(0029,"Silhouette V1.0",27) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",28) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",29) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",30) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",52) US Unknown 1 PrivateTag +(0029,"Silhouette V1.0",53) LT Unknown 1 PrivateTag +(0029,"Silhouette V1.0",54) UN Unknown 1 PrivateTag +(0029,"Silhouette V1.0",55) LT Unknown 1 PrivateTag +(0029,"Silhouette V1.0",56) LT Unknown 1 PrivateTag +(0029,"Silhouette V1.0",57) UN Unknown 1 PrivateTag + +(0135,"SONOWAND AS",10) LO UltrasoundScannerName 1 PrivateTag +(0135,"SONOWAND AS",11) LO TransducerSerial 1 PrivateTag +(0135,"SONOWAND AS",12) LO ProbeApplication 1 PrivateTag + +(0017,"SVISION",00) LO ExtendedBodyPart 1 PrivateTag +(0017,"SVISION",10) LO ExtendedViewPosition 1 PrivateTag +(0017,"SVISION",F0) IS ImagesSOPClass 1 PrivateTag +(0019,"SVISION",00) IS AECField 1 PrivateTag +(0019,"SVISION",01) IS AECFilmScreen 1 PrivateTag +(0019,"SVISION",02) IS AECDensity 1 PrivateTag +(0019,"SVISION",10) IS PatientThickness 1 PrivateTag +(0019,"SVISION",18) IS BeamDistance 1 PrivateTag +(0019,"SVISION",20) IS WorkstationNumber 1 PrivateTag +(0019,"SVISION",28) IS TubeNumber 1 PrivateTag +(0019,"SVISION",30) IS BuckyGrid 1 PrivateTag +(0019,"SVISION",34) IS Focus 1 PrivateTag +(0019,"SVISION",38) IS Child 1 PrivateTag +(0019,"SVISION",40) IS CollimatorDistanceX 1 PrivateTag +(0019,"SVISION",41) IS CollimatorDistanceY 1 PrivateTag +(0019,"SVISION",50) IS CentralBeamHeight 1 PrivateTag +(0019,"SVISION",60) IS BuckyAngle 1 PrivateTag +(0019,"SVISION",68) IS CArmAngle 1 PrivateTag +(0019,"SVISION",69) IS CollimatorAngle 1 PrivateTag +(0019,"SVISION",70) IS FilterNumber 1 PrivateTag +(0019,"SVISION",74) LO FilterMaterial1 1 PrivateTag +(0019,"SVISION",75) LO FilterMaterial2 1 PrivateTag +(0019,"SVISION",78) DS FilterThickness1 1 PrivateTag +(0019,"SVISION",79) DS FilterThickness2 1 PrivateTag +(0019,"SVISION",80) IS BuckyFormat 1 PrivateTag +(0019,"SVISION",81) IS ObjectPosition 1 PrivateTag +(0019,"SVISION",90) LO DeskCommand 1 PrivateTag +(0019,"SVISION",A0) DS ExtendedExposureTime 1 PrivateTag +(0019,"SVISION",A1) DS ActualExposureTime 1 PrivateTag +(0019,"SVISION",A8) DS ExtendedXRayTubeCurrent 1 PrivateTag +(0021,"SVISION",00) DS NoiseReduction 1 PrivateTag +(0021,"SVISION",01) DS ContrastAmplification 1 PrivateTag +(0021,"SVISION",02) DS EdgeContrastBoosting 1 PrivateTag +(0021,"SVISION",03) DS LatitudeReduction 1 PrivateTag +(0021,"SVISION",10) LO FindRangeAlgorithm 1 PrivateTag +(0021,"SVISION",11) DS ThresholdCAlgorithm 1 PrivateTag +(0021,"SVISION",20) LO SensometricCurve 1 PrivateTag +(0021,"SVISION",30) DS LowerWindowOffset 1 PrivateTag +(0021,"SVISION",31) DS UpperWindowOffset 1 PrivateTag +(0021,"SVISION",40) DS MinPrintableDensity 1 PrivateTag +(0021,"SVISION",41) DS MaxPrintableDensity 1 PrivateTag +(0021,"SVISION",90) DS Brightness 1 PrivateTag +(0021,"SVISION",91) DS Contrast 1 PrivateTag +(0021,"SVISION",92) DS ShapeFactor 1 PrivateTag +(0023,"SVISION",00) LO ImageLaterality 1 PrivateTag +(0023,"SVISION",01) IS LetterPosition 1 PrivateTag +(0023,"SVISION",02) IS BurnedInAnnotation 1 PrivateTag +(0023,"SVISION",03) LO Unknown 1 PrivateTag +(0023,"SVISION",F0) IS ImageSOPClass 1 PrivateTag +(0025,"SVISION",00) IS OriginalImage 1 PrivateTag +(0025,"SVISION",01) IS NotProcessedImage 1 PrivateTag +(0025,"SVISION",02) IS CutOutImage 1 PrivateTag +(0025,"SVISION",03) IS DuplicatedImage 1 PrivateTag +(0025,"SVISION",04) IS StoredImage 1 PrivateTag +(0025,"SVISION",05) IS RetrievedImage 1 PrivateTag +(0025,"SVISION",06) IS RemoteImage 1 PrivateTag +(0025,"SVISION",07) IS MediaStoredImage 1 PrivateTag +(0025,"SVISION",08) IS ImageState 1 PrivateTag +(0025,"SVISION",20) LO SourceImageFile 1 PrivateTag +(0025,"SVISION",21) UI Unknown 1 PrivateTag +(0027,"SVISION",00) IS NumberOfSeries 1 PrivateTag +(0027,"SVISION",01) IS NumberOfStudies 1 PrivateTag +(0027,"SVISION",10) DT OldestSeries 1 PrivateTag +(0027,"SVISION",11) DT NewestSeries 1 PrivateTag +(0027,"SVISION",12) DT OldestStudy 1 PrivateTag +(0027,"SVISION",13) DT NewestStudy 1 PrivateTag + +(0009,"TOSHIBA_MEC_1.0",01) LT Unknown 1 PrivateTag +(0009,"TOSHIBA_MEC_1.0",02) US Unknown 1-n PrivateTag +(0009,"TOSHIBA_MEC_1.0",03) US Unknown 1-n PrivateTag +(0009,"TOSHIBA_MEC_1.0",04) US Unknown 1-n PrivateTag +(0011,"TOSHIBA_MEC_1.0",01) LT Unknown 1 PrivateTag +(0011,"TOSHIBA_MEC_1.0",02) US Unknown 1-n PrivateTag +(0019,"TOSHIBA_MEC_1.0",01) US Unknown 1-n PrivateTag +(0019,"TOSHIBA_MEC_1.0",02) US Unknown 1-n PrivateTag +(0021,"TOSHIBA_MEC_1.0",01) US Unknown 1-n PrivateTag +(0021,"TOSHIBA_MEC_1.0",02) US Unknown 1-n PrivateTag +(0021,"TOSHIBA_MEC_1.0",03) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_1.0",01) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_1.0",02) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_1.0",03) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_1.0",10) US Unknown 1-n PrivateTag + +(0019,"TOSHIBA_MEC_CT_1.0",01) IS Unknown 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",02) IS Unknown 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",03) US Unknown 1-n PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",04) LT Unknown 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",05) LT Unknown 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",06) US Unknown 1-n PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",07) US Unknown 1-n PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",08) LT OrientationHeadFeet 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",09) LT ViewDirection 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",0a) LT OrientationSupineProne 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",0b) DS Unknown 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",0c) US Unknown 1-n PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",0d) TM Time 1 PrivateTag +(0019,"TOSHIBA_MEC_CT_1.0",0e) DS Unknown 1 PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",01) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",02) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",03) IS Unknown 1 PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",04) IS Unknown 1 PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",05) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",07) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",08) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",09) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",0a) LT Unknown 1 PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",0b) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",0c) US Unknown 1-n PrivateTag +(7ff1,"TOSHIBA_MEC_CT_1.0",0d) US Unknown 1-n PrivateTag +# +# end of private.dic +#
--- a/Resources/Patches/dcmtk-linux-speed.patch Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc dcmtk-3.6.0/dcmnet/libsrc/dul.cc ---- dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc 2010-12-01 09:26:36.000000000 +0100 -+++ dcmtk-3.6.0/dcmnet/libsrc/dul.cc 2015-05-15 17:03:50.762451757 +0200 -@@ -1840,7 +1840,7 @@ - } - #endif - #endif -- setTCPBufferLength(sock); -+ //setTCPBufferLength(sock); - - #ifndef DONT_DISABLE_NAGLE_ALGORITHM - /* -diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc ---- dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc 2010-12-01 09:26:36.000000000 +0100 -+++ dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc 2015-05-15 17:03:55.570451952 +0200 -@@ -2417,7 +2417,7 @@ - return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str()); - } - #endif -- setTCPBufferLength(s); -+ //setTCPBufferLength(s); - - #ifndef DONT_DISABLE_NAGLE_ALGORITHM - /*
--- a/Resources/Patches/dcmtk-mingw64.patch Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -diff -urEb dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h ---- dcmtk-3.6.0.orig/ofstd/include/dcmtk/ofstd/offile.h 2010-12-17 11:50:30.000000000 +0100 -+++ dcmtk-3.6.0/ofstd/include/dcmtk/ofstd/offile.h 2013-07-19 15:56:25.688996134 +0200 -@@ -196,7 +196,7 @@ - OFBool popen(const char *command, const char *modes) - { - if (file_) fclose(); --#ifdef _WIN32 -+#if 0 - file_ = _popen(command, modes); - #else - file_ = :: popen(command, modes); -@@ -258,7 +258,7 @@ - { - if (popened_) - { --#ifdef _WIN32 -+#if 0 - result = _pclose(file_); - #else - result = :: pclose(file_); -Only in dcmtk-3.6.0/ofstd/include/dcmtk/ofstd: offile.h~
--- a/Resources/Patches/dcmtk-mingw64.txt Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -diff -urEb dcmtk-3.6.0.orig/ dcmtk-3.6.0 > ../Resources/Patches/dcmtk-mingw64.patch
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,10 @@ +Generate some patch +=================== + +diff -urEb dcmtk-3.6.0.orig/ dcmtk-3.6.0 +diff -urEb dcmtk-3.6.2.orig/ dcmtk-3.6.2 + +For "dcmtk-3.6.2-private.dic" +============================= + +# cp ../../ThirdPartyDownloads/dcmtk-3.6.2/dcmdata/data/private.dic dcmtk-3.6.2-private.dic
--- a/Resources/Patches/glog-port-cc.diff Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ ---- i/glog-0.3.2/src/windows/port.cc 2011-08-30 09:56:13.000000000 +0200 -+++ port.cc 2012-10-03 17:11:54.000000000 +0200 -@@ -55,6 +55,7 @@ - return _vsnprintf(str, size-1, format, ap); - } - -+#if !defined(__MINGW32__) - int snprintf(char *str, size_t size, const char *format, ...) { - va_list ap; - va_start(ap, format); -@@ -62,3 +63,4 @@ - va_end(ap); - return r; - } -+#endif
--- a/Resources/Patches/glog-port-h-v2.diff Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -124,130c124,146 -< // ----------------------------------- THREADS -< typedef DWORD pthread_t; -< typedef DWORD pthread_key_t; -< typedef LONG pthread_once_t; -< enum { PTHREAD_ONCE_INIT = 0 }; // important that this be 0! for SpinLock -< #define pthread_self GetCurrentThreadId -< #define pthread_equal(pthread_t_1, pthread_t_2) ((pthread_t_1)==(pthread_t_2)) ---- -> // ----------------------------------- SECURE STRINGS -> -> #if HAVE_SECURE_STRING_EXTENSIONS == 0 -> // Emulation of "localtime_s" and "strerror_s" for old versions of MinGW -> inline int localtime_s(tm * _tm, const time_t * time) -> { -> tm * posix_local_time_struct = localtime(time); -> if (posix_local_time_struct == NULL) -> { -> return 1; -> } -> -> *_tm = *posix_local_time_struct; -> -> return 0; -> } -> -> inline char* strerror_s(char* buf, size_t buflen, int errnum) -> { -> const char* str = strerror(errnum); -> return strncpy(buf, str, buflen - 1); -> } -> #endif -131a148,149 -> -> #if !defined(__MINGW32__) || HAVE_SECURE_STRING_EXTENSIONS == 0 -135a154,155 -> #endif -> -140a161,173 -> -> -> // ----------------------------------- THREADS -> -> #if !defined(__MINGW32__) || HAVE_WIN_PTHREAD == 0 -> typedef DWORD pthread_t; -> typedef DWORD pthread_key_t; -> typedef LONG pthread_once_t; -> enum { PTHREAD_ONCE_INIT = 0 }; // important that this be 0! for SpinLock -> #define pthread_self GetCurrentThreadId -> #define pthread_equal(pthread_t_1, pthread_t_2) ((pthread_t_1)==(pthread_t_2)) -> #endif ->
--- a/Resources/Patches/glog-port-h.diff Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ ---- /tmp/m/glog-0.3.2/src/windows/port.h 2012-10-03 12:54:10.958149861 +0200 -+++ port.h 2012-10-03 16:19:56.721837994 +0200 -@@ -129,6 +129,27 @@ - #define pthread_self GetCurrentThreadId - #define pthread_equal(pthread_t_1, pthread_t_2) ((pthread_t_1)==(pthread_t_2)) - -+#if defined(__MINGW32__) -+inline int localtime_s(tm * _tm, const time_t * time) -+{ -+ tm * posix_local_time_struct = localtime(time); -+ if (posix_local_time_struct == NULL) -+ { -+ return 1; -+ } -+ -+ *_tm = *posix_local_time_struct; -+ -+ return 0; -+} -+ -+inline char* strerror_s(char* buf, size_t buflen, int errnum) -+{ -+ const char* str = strerror(errnum); -+ return strncpy(buf, str, buflen - 1); -+} -+#endif -+ - inline struct tm* localtime_r(const time_t* timep, struct tm* result) { - localtime_s(result, timep); - return result;
--- a/Resources/Patches/glog-utilities-lsb.diff Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ ---- utilities.cc.orig 2012-01-12 09:40:21.000000000 +0100 -+++ utilities.cc 2013-09-23 17:37:35.033275313 +0200 -@@ -233,40 +233,7 @@ - } - - pid_t GetTID() { -- // On Linux and FreeBSD, we try to use gettid(). --#if defined OS_LINUX || defined OS_FREEBSD || defined OS_MACOSX --#ifndef __NR_gettid --#ifdef OS_MACOSX --#define __NR_gettid SYS_gettid --#elif ! defined __i386__ --#error "Must define __NR_gettid for non-x86 platforms" --#else --#define __NR_gettid 224 --#endif --#endif -- static bool lacks_gettid = false; -- if (!lacks_gettid) { -- pid_t tid = syscall(__NR_gettid); -- if (tid != -1) { -- return tid; -- } -- // Technically, this variable has to be volatile, but there is a small -- // performance penalty in accessing volatile variables and there should -- // not be any serious adverse effect if a thread does not immediately see -- // the value change to "true". -- lacks_gettid = true; -- } --#endif // OS_LINUX || OS_FREEBSD -- -- // If gettid() could not be used, we use one of the following. --#if defined OS_LINUX -- return getpid(); // Linux: getpid returns thread ID when gettid is absent --#elif defined OS_WINDOWS || defined OS_CYGWIN -- return GetCurrentThreadId(); --#else -- // If none of the techniques above worked, we use pthread_self(). - return (pid_t)(uintptr_t)pthread_self(); --#endif - } - - const char* const_basename(const char* filepath) { -@@ -295,7 +262,7 @@ - g_my_user_name = "invalid-user"; - } - } --REGISTER_MODULE_INITIALIZER(utilities, MyUserNameInitializer()); -+REGISTER_MODULE_INITIALIZER(utilities, MyUserNameInitializer()) - - #ifdef HAVE_STACKTRACE - void DumpStackTraceToString(string* stacktrace) {
--- a/Resources/Patches/glog-utilities.diff Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ ---- /tmp/m/glog-0.3.2/src/utilities.cc 2012-01-12 09:40:21.000000000 +0100 -+++ utilities.cc 2012-10-03 16:04:19.745861665 +0200 -@@ -295,7 +295,7 @@ - g_my_user_name = "invalid-user"; - } - } -+REGISTER_MODULE_INITIALIZER(utilities, MyUserNameInitializer()) --REGISTER_MODULE_INITIALIZER(utilities, MyUserNameInitializer()); - - #ifdef HAVE_STACKTRACE - void DumpStackTraceToString(string* stacktrace) {
--- a/Resources/Patches/glog-visual-studio-port.h Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -/* Copyright (c) 2008, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * --- - * Author: Craig Silverstein - * Copied from google-perftools and modified by Shinichiro Hamaji - * - * These are some portability typedefs and defines to make it a bit - * easier to compile this code under VC++. - * - * Several of these are taken from glib: - * http://developer.gnome.org/doc/API/glib/glib-windows-compatability-functions.html - */ - -#ifndef CTEMPLATE_WINDOWS_PORT_H_ -#define CTEMPLATE_WINDOWS_PORT_H_ - -#include "config.h" - -#ifdef _WIN32 - -#define WIN32_LEAN_AND_MEAN /* We always want minimal includes */ -#include <windows.h> -#include <winsock.h> /* for gethostname */ -#include <io.h> /* because we so often use open/close/etc */ -#include <direct.h> /* for _getcwd() */ -#include <process.h> /* for _getpid() */ -#include <stdio.h> /* read in vsnprintf decl. before redifining it */ -#include <stdarg.h> /* template_dictionary.cc uses va_copy */ -#include <string.h> /* for _strnicmp(), strerror_s() */ -#include <time.h> /* for localtime_s() */ -/* Note: the C++ #includes are all together at the bottom. This file is - * used by both C and C++ code, so we put all the C++ together. - */ - -// Fix by Sebastien Jodogne for Visual Studio 2013 -// https://code.google.com/p/google-glog/issues/detail?id=212 -#if defined(_MSC_VER) && (_MSC_VER >= 1800) -#include <algorithm> -#endif - -/* 4244: otherwise we get problems when substracting two size_t's to an int - * 4251: it's complaining about a private struct I've chosen not to dllexport - * 4355: we use this in a constructor, but we do it safely - * 4715: for some reason VC++ stopped realizing you can't return after abort() - * 4800: we know we're casting ints/char*'s to bools, and we're ok with that - * 4996: Yes, we're ok using "unsafe" functions like fopen() and strerror() - */ -#pragma warning(disable:4244 4251 4355 4715 4800 4996) - -/* file I/O */ -#define PATH_MAX 1024 -#define access _access -#define getcwd _getcwd -#define open _open -#define read _read -#define write _write -#define lseek _lseek -#define close _close -#define popen _popen -#define pclose _pclose -#define R_OK 04 /* read-only (for access()) */ -#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -#ifndef __MINGW32__ -enum { STDIN_FILENO = 0, STDOUT_FILENO = 1, STDERR_FILENO = 2 }; -#endif -#define S_IRUSR S_IREAD -#define S_IWUSR S_IWRITE - -/* Not quite as lightweight as a hard-link, but more than good enough for us. */ -#define link(oldpath, newpath) CopyFileA(oldpath, newpath, false) - -#define strcasecmp _stricmp -#define strncasecmp _strnicmp - -/* In windows-land, hash<> is called hash_compare<> (from xhash.h) */ -#if _MSC_VER < 1700 -#define hash hash_compare -#endif - -/* Sleep is in ms, on windows */ -#define sleep(secs) Sleep((secs) * 1000) - -/* We can't just use _vsnprintf and _snprintf as drop-in-replacements, - * because they don't always NUL-terminate. :-( We also can't use the - * name vsnprintf, since windows defines that (but not snprintf (!)). - */ -extern int snprintf(char *str, size_t size, - const char *format, ...); -extern int safe_vsnprintf(char *str, size_t size, - const char *format, va_list ap); -#define vsnprintf(str, size, format, ap) safe_vsnprintf(str, size, format, ap) -#define va_copy(dst, src) (dst) = (src) - -/* Windows doesn't support specifying the number of buckets as a - * hash_map constructor arg, so we leave this blank. - */ -#define CTEMPLATE_SMALL_HASHTABLE - -#define DEFAULT_TEMPLATE_ROOTDIR ".." - -// ----------------------------------- SYSTEM/PROCESS -typedef int pid_t; -#define getpid _getpid - -// ----------------------------------- THREADS -typedef DWORD pthread_t; -typedef DWORD pthread_key_t; -typedef LONG pthread_once_t; -enum { PTHREAD_ONCE_INIT = 0 }; // important that this be 0! for SpinLock -#define pthread_self GetCurrentThreadId -#define pthread_equal(pthread_t_1, pthread_t_2) ((pthread_t_1)==(pthread_t_2)) - -#if defined(__MINGW32__) -inline int localtime_s(tm * _tm, const time_t * time) -{ - tm * posix_local_time_struct = localtime(time); - if (posix_local_time_struct == NULL) - { - return 1; - } - - *_tm = *posix_local_time_struct; - - return 0; -} - -inline char* strerror_s(char* buf, size_t buflen, int errnum) -{ - const char* str = strerror(errnum); - return strncpy(buf, str, buflen - 1); -} -#endif - -inline struct tm* localtime_r(const time_t* timep, struct tm* result) { - localtime_s(result, timep); - return result; -} - -inline char* strerror_r(int errnum, char* buf, size_t buflen) { - strerror_s(buf, buflen, errnum); - return buf; -} - -#ifndef __cplusplus -/* I don't see how to get inlining for C code in MSVC. Ah well. */ -#define inline -#endif - -#endif /* _WIN32 */ - -#endif /* CTEMPLATE_WINDOWS_PORT_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/libp11-0.4.0.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,36 @@ +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 +@@ -25,7 +25,7 @@ + #include <sys/stat.h> + #include <sys/types.h> + #include <unistd.h> +-#include <atfork.h> ++#include "atfork.h" + + #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 +@@ -29,7 +29,7 @@ + #define _ENGINE_PKCS11_H + + #ifndef _WIN32 +-#include "config.h" ++//#include "config.h" + #endif + + #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 +@@ -20,7 +20,7 @@ + #define _LIBP11_INT_H + + #ifndef _WIN32 +-#include "config.h" ++//#include "config.h" + #endif + + #include "libp11.h"
--- a/Resources/Patches/log4cplus-patch.diff Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ ---- log4cplus-1.0.4.1/src/factory.cxx 2010-05-28 11:06:51.000000000 +0200 -+++ factory.cxx 2012-10-02 11:43:31.808439489 +0200 -@@ -35,7 +35,7 @@ - # if defined (LOG4CPLUS_HAVE_WIN32_CONSOLE) - # include <log4cplus/win32consoleappender.h> - # endif --# include <log4cplus/Win32DebugAppender.h> -+# include <log4cplus/win32debugappender.h> - #endif -
--- a/Resources/Patches/mongoose-3.1-patch.diff Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Patches/mongoose-3.1-patch.diff Fri Oct 12 15:18:10 2018 +0200 @@ -42,7 +42,7 @@ // Wait until mg_fini() stops while (ctx->stop_flag != 2) { -+#if defined(__linux) ++#if defined(__linux__) + usleep(100000); +#elif defined(_WIN32) + Sleep(100);
--- a/Resources/Patches/openssl-lsb.diff Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ ---- ui_openssl.c.orig 2013-09-24 15:06:54.264420779 +0200 -+++ ui_openssl.c 2013-09-24 14:22:43.512312998 +0200 -@@ -291,7 +291,7 @@ - static unsigned short channel = 0; - #else - #if !defined(OPENSSL_SYS_MSDOS) || defined(__DJGPP__) --static TTY_STRUCT tty_orig,tty_new; -+//static TTY_STRUCT tty_orig,tty_new; - #endif - #endif - static FILE *tty_in, *tty_out; -@@ -475,106 +475,21 @@ - /* Internal functions to open, handle and close a channel to the console. */ - static int open_console(UI *ui) - { -- CRYPTO_w_lock(CRYPTO_LOCK_UI); -- is_a_tty = 1; -- --#if defined(OPENSSL_SYS_MACINTOSH_CLASSIC) || defined(OPENSSL_SYS_VXWORKS) || defined(OPENSSL_SYS_NETWARE) || defined(OPENSSL_SYS_BEOS) -- tty_in=stdin; -- tty_out=stderr; --#else --# ifdef OPENSSL_SYS_MSDOS --# define DEV_TTY "con" --# else --# define DEV_TTY "/dev/tty" --# endif -- if ((tty_in=fopen(DEV_TTY,"r")) == NULL) -- tty_in=stdin; -- if ((tty_out=fopen(DEV_TTY,"w")) == NULL) -- tty_out=stderr; --#endif -- --#if defined(TTY_get) && !defined(OPENSSL_SYS_VMS) -- if (TTY_get(fileno(tty_in),&tty_orig) == -1) -- { --#ifdef ENOTTY -- if (errno == ENOTTY) -- is_a_tty=0; -- else --#endif --#ifdef EINVAL -- /* Ariel Glenn ariel@columbia.edu reports that solaris -- * can return EINVAL instead. This should be ok */ -- if (errno == EINVAL) -- is_a_tty=0; -- else --#endif -- return 0; -- } --#endif --#ifdef OPENSSL_SYS_VMS -- status = sys$assign(&terminal,&channel,0,0); -- if (status != SS$_NORMAL) -- return 0; -- status=sys$qiow(0,channel,IO$_SENSEMODE,&iosb,0,0,tty_orig,12,0,0,0,0); -- if ((status != SS$_NORMAL) || (iosb.iosb$w_value != SS$_NORMAL)) -- return 0; --#endif - return 1; - } - - static int noecho_console(UI *ui) - { --#ifdef TTY_FLAGS -- memcpy(&(tty_new),&(tty_orig),sizeof(tty_orig)); -- tty_new.TTY_FLAGS &= ~ECHO; --#endif -- --#if defined(TTY_set) && !defined(OPENSSL_SYS_VMS) -- if (is_a_tty && (TTY_set(fileno(tty_in),&tty_new) == -1)) -- return 0; --#endif --#ifdef OPENSSL_SYS_VMS -- tty_new[0] = tty_orig[0]; -- tty_new[1] = tty_orig[1] | TT$M_NOECHO; -- tty_new[2] = tty_orig[2]; -- status = sys$qiow(0,channel,IO$_SETMODE,&iosb,0,0,tty_new,12,0,0,0,0); -- if ((status != SS$_NORMAL) || (iosb.iosb$w_value != SS$_NORMAL)) -- return 0; --#endif - return 1; - } - - static int echo_console(UI *ui) - { --#if defined(TTY_set) && !defined(OPENSSL_SYS_VMS) -- memcpy(&(tty_new),&(tty_orig),sizeof(tty_orig)); -- tty_new.TTY_FLAGS |= ECHO; --#endif -- --#if defined(TTY_set) && !defined(OPENSSL_SYS_VMS) -- if (is_a_tty && (TTY_set(fileno(tty_in),&tty_new) == -1)) -- return 0; --#endif --#ifdef OPENSSL_SYS_VMS -- tty_new[0] = tty_orig[0]; -- tty_new[1] = tty_orig[1] & ~TT$M_NOECHO; -- tty_new[2] = tty_orig[2]; -- status = sys$qiow(0,channel,IO$_SETMODE,&iosb,0,0,tty_new,12,0,0,0,0); -- if ((status != SS$_NORMAL) || (iosb.iosb$w_value != SS$_NORMAL)) -- return 0; --#endif - return 1; - } - - static int close_console(UI *ui) - { -- if (tty_in != stdin) fclose(tty_in); -- if (tty_out != stderr) fclose(tty_out); --#ifdef OPENSSL_SYS_VMS -- status = sys$dassgn(channel); --#endif -- CRYPTO_w_unlock(CRYPTO_LOCK_UI); -- - return 1; - } -
--- a/Resources/RetrieveCACertificates.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/RetrieveCACertificates.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # 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/ImportDicomFiles/ImportDicomFiles.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ -#!/usr/bin/python +#!/usr/bin/env python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ Usage: %s [hostname] [HTTP port] [path] Usage: %s [hostname] [HTTP port] [path] [username] [password] -For instance: %s localhost 8042 . +For instance: %s 127.0.0.1 8042 . """ % (sys.argv[0], sys.argv[0], sys.argv[0])) exit(-1) @@ -81,7 +82,7 @@ sys.stdout.write(" => success\n") success_count += 1 else: - sys.stdout.write(" => failure (Is it a DICOM file?)\n") + sys.stdout.write(" => failure (Is it a DICOM file? Is there a password?)\n") except: sys.stdout.write(" => unable to connect (Is Orthanc running? Is there a password?)\n")
--- a/Resources/Samples/Lua/AutomatedJpeg2kCompression.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/AutomatedJpeg2kCompression.lua Fri Oct 12 15:18:10 2018 +0200 @@ -16,7 +16,7 @@ -- Compress to JPEG2000 using gdcm local compressed = instanceId .. '-compressed.dcm' - os.execute('gdcmconv --j2k ' .. uncompressed .. ' ' .. compressed) + os.execute('gdcmconv -U --j2k ' .. uncompressed .. ' ' .. compressed) -- Generate a new SOPInstanceUID for the JPEG2000 file, as -- gdcmconv does not do this by itself
--- a/Resources/Samples/Lua/AutoroutingModification.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/AutoroutingModification.lua Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,7 @@ -function OnStoredInstance(instanceId, tags, metadata) - -- Ignore the instances that result from a modification to avoid - -- infinite loops - if (metadata['ModifiedFrom'] == nil and - metadata['AnonymizedFrom'] == nil) then +function OnStoredInstance(instanceId, tags, metadata, origin) + -- Ignore the instances that result from the present Lua script to + -- avoid infinite loops + if origin['RequestOrigin'] ~= 'Lua' then -- The tags to be replaced local replace = {} @@ -12,10 +11,21 @@ -- The tags to be removed local remove = { 'MilitaryRank' } - -- Modify the instance, send it, then delete the modified instance - Delete(SendToModality(ModifyInstance(instanceId, replace, remove, true), 'sample')) + -- Modify the instance + local command = {} + command['Replace'] = replace + command['Remove'] = remove + local modifiedFile = RestApiPost('/instances/' .. instanceId .. '/modify', DumpJson(command, true)) - -- Delete the original instance - Delete(instanceId) + -- Upload the modified instance to the Orthanc database so that + -- it can be sent by Orthanc to other modalities + local modifiedId = ParseJson(RestApiPost('/instances/', modifiedFile)) ['ID'] + + -- Send the modified instance to another modality + RestApiPost('/modalities/sample/store', modifiedId) + + -- Delete the original and the modified instances + RestApiDelete('/instances/' .. instanceId) + RestApiDelete('/instances/' .. modifiedId) end end
--- a/Resources/Samples/Lua/CallWebService.js Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/CallWebService.js Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/CallWebService.lua Fri Oct 12 15:18:10 2018 +0200 @@ -17,7 +17,7 @@ info['PatientID'] = tags['PatientID'] -- Send the POST request - local answer = HttpPost('http://localhost:8000/', JSON:encode(info)) + local answer = HttpPost('http://127.0.0.1:8000/', JSON:encode(info)) -- The answer equals "ERROR" in case of an error print('Web service called, answer received: ' .. answer)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/IncomingFindRequestFilter.lua Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,14 @@ +-- This example solves the following problem: +-- https://groups.google.com/d/msg/orthanc-users/PLWKqVVaXLs/n_0x4vKhAgAJ + +function IncomingFindRequestFilter(source, origin) + -- First display the content of the C-Find query + PrintRecursive(source) + PrintRecursive(origin) + + -- Remove the "PrivateCreator" tag from the query + local v = source + v['5555,0010'] = nil + + return v +end
--- a/Resources/Samples/Lua/ModifyInstanceWithSequence.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/ModifyInstanceWithSequence.lua Fri Oct 12 15:18:10 2018 +0200 @@ -17,7 +17,7 @@ -- Create the modified instance local modified = RestApiPost('/instances/' .. instanceId .. '/modify', - DumpJson(request)) + DumpJson(request, true)) -- Upload the modified instance to the Orthanc store RestApiPost('/instances/', modified)
--- a/Resources/Samples/Lua/OnStableStudy.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/OnStableStudy.lua Fri Oct 12 15:18:10 2018 +0200 @@ -40,7 +40,7 @@ -- Modify the entire study in one single call local m = RestApiPost('/studies/' .. studyId .. '/modify', - DumpJson(command)) + DumpJson(command, true)) print('Modified study: ' .. m) end end
--- a/Resources/Samples/Lua/TransferSyntaxDisable.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/TransferSyntaxDisable.lua Fri Oct 12 15:18:10 2018 +0200 @@ -26,4 +26,8 @@ return false end +function IsUnknownSopClassAccepted(aet, ip) + return false +end + print('All special transfer syntaxes are now disallowed')
--- a/Resources/Samples/Lua/TransferSyntaxEnable.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/TransferSyntaxEnable.lua Fri Oct 12 15:18:10 2018 +0200 @@ -26,4 +26,8 @@ return true end +function IsUnknownSopClassAccepted(aet, ip) + return true +end + print('All special transfer syntaxes are now accepted')
--- a/Resources/Samples/Lua/WriteToDisk.lua Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Lua/WriteToDisk.lua Fri Oct 12 15:18:10 2018 +0200 @@ -2,7 +2,8 @@ function ToAscii(s) -- http://www.lua.org/manual/5.1/manual.html#pdf-string.gsub - return s:gsub('[^a-zA-Z0-9-/ ]', '_') + -- https://groups.google.com/d/msg/orthanc-users/qMLgkEmwwPI/6jRpCrlgBwAJ + return s:gsub('[^a-zA-Z0-9-/-: ]', '_') end function OnStableSeries(seriesId, tags, metadata)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancFramework/MicroService/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.8) + +project(Sample) + +include(${CMAKE_SOURCE_DIR}/../../../CMake/OrthancFrameworkParameters.cmake) + +set(ENABLE_WEB_SERVER ON) + +include(${CMAKE_SOURCE_DIR}/../../../CMake/OrthancFrameworkConfiguration.cmake) + +add_executable(Sample + ${ORTHANC_CORE_SOURCES} + Sample.cpp + ) + +include_directories(${ORTHANC_ROOT}/Core)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancFramework/MicroService/README.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,2 @@ +This file shows how to create a simple Web service in C++ (similar to +Python's Flask) using the Orthanc standalone framework.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/OrthancFramework/MicroService/Sample.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,61 @@ +#include <stdio.h> + +#include <HttpServer/MongooseServer.h> +#include <Logging.h> +#include <RestApi/RestApi.h> +#include <SystemToolbox.h> + +class MicroService : public Orthanc::RestApi +{ +private: + static MicroService& GetSelf(Orthanc::RestApiCall& call) + { + return dynamic_cast<MicroService&>(call.GetContext()); + } + + void SayHello() + { + printf("Hello\n"); + } + + static void Hello(Orthanc::RestApiGetCall& call) + { + GetSelf(call).SayHello(); + + Json::Value value = Json::arrayValue; + value.append("World"); + + call.GetOutput().AnswerJson(value); + } + +public: + MicroService() + { + Register("/hello", Hello); + } +}; + +int main() +{ + Orthanc::Logging::Initialize(); + Orthanc::Logging::EnableTraceLevel(true); + + MicroService rest; + + { + Orthanc::MongooseServer httpServer; + httpServer.SetPortNumber(8000); + httpServer.Register(rest); + httpServer.SetRemoteAccessAllowed(true); + httpServer.Start(); + + LOG(WARNING) << "Micro-service started on port " << httpServer.GetPortNumber(); + Orthanc::SystemToolbox::ServerBarrier(); + } + + LOG(WARNING) << "Micro-service stopped"; + + Orthanc::Logging::Finalize(); + + return 0; +}
--- a/Resources/Samples/Python/AnonymizeAllPatients.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/AnonymizeAllPatients.py Fri Oct 12 15:18:10 2018 +0200 @@ -2,8 +2,9 @@ # -*- coding: utf-8 -*- # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -20,7 +21,7 @@ -URL = 'http://localhost:8042' +URL = 'http://127.0.0.1:8042' # # This sample code will anonymize all the patients that are stored in
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Python/ArchiveAllPatients.py Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,48 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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/>. + + + +import os +import os.path +import sys +import RestToolbox + +def PrintHelp(): + print('Download one ZIP archive for all the patients stored in Orthanc\n') + print('Usage: %s <URL> <Target>\n' % sys.argv[0]) + print('Example: %s http://127.0.0.1:8042/ /tmp/Archive.zip\n' % sys.argv[0]) + exit(-1) + +if len(sys.argv) != 3: + PrintHelp() + +URL = sys.argv[1] +TARGET = sys.argv[2] + +patients = RestToolbox.DoGet('%s/patients' % URL) + +print('Downloading ZIP...') +zipContent = RestToolbox.DoPost('%s/tools/create-archive' % URL, patients) + +# Write the ZIP archive at the proper location +with open(TARGET, 'wb') as f: + f.write(zipContent)
--- a/Resources/Samples/Python/ArchiveStudiesInTimeRange.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/ArchiveStudiesInTimeRange.py Fri Oct 12 15:18:10 2018 +0200 @@ -2,8 +2,9 @@ # -*- coding: utf-8 -*- # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -29,7 +30,7 @@ print('Download ZIP archives for all the studies generated ' 'during a given time range (according to the StudyDate tag)\n') print('Usage: %s <URL> <StartDate> <EndDate> <TargetFolder>\n' % sys.argv[0]) - print('Example: %s http://localhost:8042/ 20150101 20151231 /tmp/\n' % sys.argv[0]) + print('Example: %s http://127.0.0.1:8042/ 20150101 20151231 /tmp/\n' % sys.argv[0]) exit(-1) def CheckIsDate(date): @@ -49,6 +50,12 @@ CheckIsDate(START) CheckIsDate(END) +def GetTag(tags, key): + if key in tags: + return tags[key] + else: + return 'No%s' % key + # Loop over the studies for studyId in RestToolbox.DoGet('%s/studies' % URL): # Retrieve the DICOM tags of the current study @@ -61,13 +68,13 @@ studyDate = study['StudyDate'][:8] if studyDate >= START and studyDate <= END: # Create a filename - filename = '%s - %s %s - %s.zip' % (study['StudyDate'], - patient['PatientID'], - patient['PatientName'], - study['StudyDescription']) + filename = '%s - %s %s - %s.zip' % (GetTag(study, 'StudyDate'), + GetTag(patient, 'PatientID'), + GetTag(patient, 'PatientName'), + GetTag(study, 'StudyDescription')) # Remove any non-ASCII character in the filename - filename = filename.encode('ascii', errors = 'replace') + filename = filename.encode('ascii', errors = 'replace').translate(None, r"'\/:*?\"<>|!=").strip() # Download the ZIP archive of the study print('Downloading %s' % filename)
--- a/Resources/Samples/Python/AutoClassify.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/AutoClassify.py Fri Oct 12 15:18:10 2018 +0200 @@ -2,8 +2,9 @@ # -*- coding: utf-8 -*- # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -30,7 +31,7 @@ description = 'Automated classification of DICOM files from Orthanc.', formatter_class = argparse.ArgumentDefaultsHelpFormatter) -parser.add_argument('--host', default = 'localhost', +parser.add_argument('--host', default = '127.0.0.1', help = 'The host address that runs Orthanc') parser.add_argument('--port', type = int, default = '8042', help = 'The port number to which Orthanc is listening for the REST API') @@ -45,7 +46,7 @@ def FixPath(p): - return p.encode('ascii', 'ignore').strip().decode() + return p.encode('ascii', errors = 'replace').translate(None, r"'\/:*?\"<>|!=").strip() def GetTag(resource, tag): if ('MainDicomTags' in resource and @@ -110,13 +111,17 @@ if change['ChangeType'] == 'NewInstance': try: ClassifyInstance(change['ID']) + + # If requested, remove the instance once it has been + # properly handled by "ClassifyInstance()". Thanks to + # the "try/except" block, the instance is not removed + # if the "ClassifyInstance()" function fails. + if args.remove: + RestToolbox.DoDelete('%s/instances/%s' % (URL, change['ID'])) + except: print('Unable to write instance %s to the disk' % change['ID']) - # If requested, remove the instance once it has been copied - if args.remove: - RestToolbox.DoDelete('%s/instances/%s' % (URL, change['ID'])) - current = r['Last'] if r['Done']:
--- a/Resources/Samples/Python/ChangesLoop.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/ChangesLoop.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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,7 +35,7 @@ images into Orthanc (through the Changes API). Usage: %s [hostname] [HTTP port] -For instance: %s localhost 8042 +For instance: %s 127.0.0.1 8042 """ % (sys.argv[0], sys.argv[0])) exit(-1)
--- a/Resources/Samples/Python/ContinuousPatientAnonymization.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/ContinuousPatientAnonymization.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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,7 +38,7 @@ the configuration option "StableAge"). Usage: %s [hostname] [HTTP port] -For instance: %s localhost 8042 +For instance: %s 127.0.0.1 8042 """ % (sys.argv[0], sys.argv[0])) exit(-1)
--- a/Resources/Samples/Python/DownloadAnonymized.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/DownloadAnonymized.py Fri Oct 12 15:18:10 2018 +0200 @@ -2,8 +2,9 @@ # -*- coding: utf-8 -*- # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -20,7 +21,7 @@ -URL = 'http://localhost:8042' +URL = 'http://127.0.0.1:8042' # # This sample code will download a ZIP file for each patient that has @@ -44,6 +45,5 @@ # Trigger the download print('Downloading %s' % name) zipContent = RestToolbox.DoGet('%s/patients/%s/archive' % (URL, patient)) - f = open(os.path.join('/tmp', name + '.zip'), 'wb') - f.write(zipContent) - f.close() + with open(os.path.join('/tmp', name + '.zip'), 'wb') as f: + f.write(zipContent)
--- a/Resources/Samples/Python/HighPerformanceAutoRouting.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py Fri Oct 12 15:18:10 2018 +0200 @@ -2,8 +2,9 @@ # -*- coding: utf-8 -*- # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -20,7 +21,7 @@ -URL = 'http://localhost:8042' +URL = 'http://127.0.0.1:8042' TARGET = 'sample'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Python/ManualModification.py Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,68 @@ +#!/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-2018 Osimis S.A., 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/>. + + + +# This sample shows how to carry on a manual modification of DICOM +# tags spread accross various levels (Patient/Study/Series/Instance) +# that would normally forbidden as such by the REST API of Orthanc to +# avoid breaking the DICOM hierarchy. This sample can be useful for +# more complex anonymization/modification scenarios, or for optimizing +# the disk usage (the original and the modified instances never +# coexist). + +from RestToolbox import * + +URL = 'http://127.0.0.1:8042' +STUDY = '27f7126f-4f66fb14-03f4081b-f9341db2-53925988' + +identifiers = {} + +for instance in DoGet('%s/studies/%s/instances' % (URL, STUDY)): + # Setup the parameters of the modification + replace = { + "PatientID" : "Hello", + "PatientName" : "Modified", + "StationName" : "TEST", + } + + # Get the original UIDs of the instance + seriesUID = DoGet('%s/instances/%s/content/SeriesInstanceUID' % (URL, instance['ID'])) + if seriesUID in identifiers: + replace['SeriesInstanceUID'] = identifiers[seriesUID] + + studyUID = DoGet('%s/instances/%s/content/StudyInstanceUID' % (URL, instance['ID'])) + if studyUID in identifiers: + replace['StudyInstanceUID'] = identifiers[studyUID] + + # Manually modify the instance + print('Modifying instance %s' % instance['ID']) + modified = DoPost('%s/instances/%s/modify' % (URL, instance['ID']), + { "Replace" : replace }) + + # Remove the original instance + DoDelete('%s/instances/%s' % (URL, instance['ID'])) + + # Add the modified instance + modifiedId = DoPost('%s/instances' % URL, modified)['ID'] + + # Register the modified UIDs + identifiers[seriesUID] = DoGet('%s/instances/%s/content/SeriesInstanceUID' % (URL, modifiedId)) + identifiers[studyUID] = DoGet('%s/instances/%s/content/StudyInstanceUID' % (URL, modifiedId))
--- a/Resources/Samples/Python/Replicate.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/Replicate.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ server through their REST API. Usage: %s [SourceURI] [TargetURI] -For instance: %s http://orthanc:password@localhost:8042/ http://localhost:8043/ +For instance: %s http://orthanc:password@127.0.0.1:8042/ http://127.0.0.1:8043/ """ % (sys.argv[0], sys.argv[0])) exit(-1)
--- a/Resources/Samples/Python/RestToolbox.py Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Python/RestToolbox.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,6 +1,7 @@ # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -48,14 +49,17 @@ if _credentials != None: h.add_credentials(_credentials[0], _credentials[1]) -def DoGet(uri, data = {}, interpretAsJson = True): +def _ComputeGetUri(uri, data): d = '' if len(data.keys()) > 0: d = '?' + urlencode(data) + return uri + d + +def DoGet(uri, data = {}, interpretAsJson = True): h = httplib2.Http() _SetupCredentials(h) - resp, content = h.request(uri + d, 'GET') + resp, content = h.request(_ComputeGetUri(uri, data), 'GET') if not (resp.status in [ 200 ]): raise Exception(resp.status) elif not interpretAsJson: @@ -64,6 +68,16 @@ return _DecodeJson(content) +def DoRawGet(uri, data = {}): + h = httplib2.Http() + _SetupCredentials(h) + resp, content = h.request(_ComputeGetUri(uri, data), 'GET') + if not (resp.status in [ 200 ]): + raise Exception(resp.status) + else: + return content + + def _DoPutOrPost(uri, method, data, contentType): h = httplib2.Http() _SetupCredentials(h)
--- a/Resources/Samples/Tools/CMakeLists.txt Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Tools/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -8,34 +8,29 @@ link_libraries(pthread dl) endif() +include(${CMAKE_SOURCE_DIR}/../../CMake/OrthancFrameworkParameters.cmake) + set(STATIC_BUILD ON) set(ALLOW_DOWNLOADS ON) -set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) - -include(CheckIncludeFiles) -include(CheckIncludeFileCXX) -include(CheckLibraryExists) -include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/../../CMake/OrthancFrameworkConfiguration.cmake) add_library(CommonLibraries ${BOOST_SOURCES} - ${THIRD_PARTY_SOURCES} - ${ORTHANC_ROOT}/Core/OrthancException.cpp + ${JSONCPP_SOURCES} + ${ORTHANC_ROOT}/Core/Enumerations.cpp + ${ORTHANC_ROOT}/Core/Logging.cpp + ${ORTHANC_ROOT}/Core/SystemToolbox.cpp ${ORTHANC_ROOT}/Core/Toolbox.cpp - ${ORTHANC_ROOT}/Core/Uuid.cpp ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp ) add_executable(RecoverCompressedFile RecoverCompressedFile.cpp - ${ORTHANC_ROOT}/Core/Compression/BufferCompressor.cpp + ${ORTHANC_ROOT}/Core/Compression/DeflateBaseCompressor.cpp ${ORTHANC_ROOT}/Core/Compression/ZlibCompressor.cpp + ${ZLIB_SOURCES} ) -target_link_libraries(RecoverCompressedFile CommonLibraries GoogleLog) +target_link_libraries(RecoverCompressedFile CommonLibraries)
--- a/Resources/Samples/Tools/RecoverCompressedFile.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/Tools/RecoverCompressedFile.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +20,7 @@ #include "../../../Core/Compression/ZlibCompressor.h" -#include "../../../Core/Toolbox.h" +#include "../../../Core/SystemToolbox.h" #include "../../../Core/OrthancException.h" #include <stdio.h> @@ -40,21 +41,23 @@ fflush(stderr); std::string content; - Orthanc::Toolbox::ReadFile(content, argv[1]); + Orthanc::SystemToolbox::ReadFile(content, argv[1]); fprintf(stderr, "Decompressing the content of the file...\n"); fflush(stderr); Orthanc::ZlibCompressor compressor; std::string uncompressed; - compressor.Uncompress(uncompressed, content); + compressor.Uncompress(uncompressed, + content.empty() ? NULL : content.c_str(), + content.size()); fprintf(stderr, "Writing the uncompressed data...\n"); fflush(stderr); if (argc == 3) { - Orthanc::Toolbox::WriteFile(uncompressed, argv[2]); + Orthanc::SystemToolbox::WriteFile(uncompressed, argv[2]); } else {
--- a/Resources/Samples/WebApplications/DrawingDicomizer.js Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/WebApplications/DrawingDicomizer.js Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +59,7 @@ toolbox.ServeFile('DrawingDicomizer/orthanc.js', response); } else if (req.url == '/jquery.js') { - toolbox.ServeFile('../../../OrthancExplorer/libs/jquery-1.7.2.min.js', response); + toolbox.ServeFile('../../../OrthancExplorer/libs/jquery.min.js', response); } else if (req.url.startsWith('/orthanc')) { toolbox.ForwardGetRequest(orthanc, req.url.substr(8), response); @@ -96,4 +97,6 @@ } }); + +console.log('The demo is running at http://localhost:' + port + '/'); server.listen(port);
--- a/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/Samples/WebApplications/NodeToolbox.js Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/ThirdParty/VisualStudio/stdint.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/ThirdParty/VisualStudio/stdint.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,247 +1,259 @@ -// ISO C9x compliant stdint.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006-2008 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_STDINT_H_ // [ -#define _MSC_STDINT_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#include <limits.h> - -// For Visual Studio 6 in C++ mode and for many Visual Studio versions when -// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}' -// or compiler give many errors like this: -// error C2733: second C linkage of overloaded function 'wmemchr' not allowed -#ifdef __cplusplus -extern "C" { -#endif -# include <wchar.h> -#ifdef __cplusplus -} -#endif - -// Define _W64 macros to mark types changing their size, like intptr_t. -#ifndef _W64 -# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 -# define _W64 __w64 -# else -# define _W64 -# endif -#endif - - -// 7.18.1 Integer types - -// 7.18.1.1 Exact-width integer types - -// Visual Studio 6 and Embedded Visual C++ 4 doesn't -// realize that, e.g. char has the same size as __int8 -// so we give up on __intX for them. -#if (_MSC_VER < 1300) - typedef signed char int8_t; - typedef signed short int16_t; - typedef signed int int32_t; - typedef unsigned char uint8_t; - typedef unsigned short uint16_t; - typedef unsigned int uint32_t; -#else - typedef signed __int8 int8_t; - typedef signed __int16 int16_t; - typedef signed __int32 int32_t; - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; -#endif -typedef signed __int64 int64_t; -typedef unsigned __int64 uint64_t; - - -// 7.18.1.2 Minimum-width integer types -typedef int8_t int_least8_t; -typedef int16_t int_least16_t; -typedef int32_t int_least32_t; -typedef int64_t int_least64_t; -typedef uint8_t uint_least8_t; -typedef uint16_t uint_least16_t; -typedef uint32_t uint_least32_t; -typedef uint64_t uint_least64_t; - -// 7.18.1.3 Fastest minimum-width integer types -typedef int8_t int_fast8_t; -typedef int16_t int_fast16_t; -typedef int32_t int_fast32_t; -typedef int64_t int_fast64_t; -typedef uint8_t uint_fast8_t; -typedef uint16_t uint_fast16_t; -typedef uint32_t uint_fast32_t; -typedef uint64_t uint_fast64_t; - -// 7.18.1.4 Integer types capable of holding object pointers -#ifdef _WIN64 // [ - typedef signed __int64 intptr_t; - typedef unsigned __int64 uintptr_t; -#else // _WIN64 ][ - typedef _W64 signed int intptr_t; - typedef _W64 unsigned int uintptr_t; -#endif // _WIN64 ] - -// 7.18.1.5 Greatest-width integer types -typedef int64_t intmax_t; -typedef uint64_t uintmax_t; - - -// 7.18.2 Limits of specified-width integer types - -#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 - -// 7.18.2.1 Limits of exact-width integer types -#define INT8_MIN ((int8_t)_I8_MIN) -#define INT8_MAX _I8_MAX -#define INT16_MIN ((int16_t)_I16_MIN) -#define INT16_MAX _I16_MAX -#define INT32_MIN ((int32_t)_I32_MIN) -#define INT32_MAX _I32_MAX -#define INT64_MIN ((int64_t)_I64_MIN) -#define INT64_MAX _I64_MAX -#define UINT8_MAX _UI8_MAX -#define UINT16_MAX _UI16_MAX -#define UINT32_MAX _UI32_MAX -#define UINT64_MAX _UI64_MAX - -// 7.18.2.2 Limits of minimum-width integer types -#define INT_LEAST8_MIN INT8_MIN -#define INT_LEAST8_MAX INT8_MAX -#define INT_LEAST16_MIN INT16_MIN -#define INT_LEAST16_MAX INT16_MAX -#define INT_LEAST32_MIN INT32_MIN -#define INT_LEAST32_MAX INT32_MAX -#define INT_LEAST64_MIN INT64_MIN -#define INT_LEAST64_MAX INT64_MAX -#define UINT_LEAST8_MAX UINT8_MAX -#define UINT_LEAST16_MAX UINT16_MAX -#define UINT_LEAST32_MAX UINT32_MAX -#define UINT_LEAST64_MAX UINT64_MAX - -// 7.18.2.3 Limits of fastest minimum-width integer types -#define INT_FAST8_MIN INT8_MIN -#define INT_FAST8_MAX INT8_MAX -#define INT_FAST16_MIN INT16_MIN -#define INT_FAST16_MAX INT16_MAX -#define INT_FAST32_MIN INT32_MIN -#define INT_FAST32_MAX INT32_MAX -#define INT_FAST64_MIN INT64_MIN -#define INT_FAST64_MAX INT64_MAX -#define UINT_FAST8_MAX UINT8_MAX -#define UINT_FAST16_MAX UINT16_MAX -#define UINT_FAST32_MAX UINT32_MAX -#define UINT_FAST64_MAX UINT64_MAX - -// 7.18.2.4 Limits of integer types capable of holding object pointers -#ifdef _WIN64 // [ -# define INTPTR_MIN INT64_MIN -# define INTPTR_MAX INT64_MAX -# define UINTPTR_MAX UINT64_MAX -#else // _WIN64 ][ -# define INTPTR_MIN INT32_MIN -# define INTPTR_MAX INT32_MAX -# define UINTPTR_MAX UINT32_MAX -#endif // _WIN64 ] - -// 7.18.2.5 Limits of greatest-width integer types -#define INTMAX_MIN INT64_MIN -#define INTMAX_MAX INT64_MAX -#define UINTMAX_MAX UINT64_MAX - -// 7.18.3 Limits of other integer types - -#ifdef _WIN64 // [ -# define PTRDIFF_MIN _I64_MIN -# define PTRDIFF_MAX _I64_MAX -#else // _WIN64 ][ -# define PTRDIFF_MIN _I32_MIN -# define PTRDIFF_MAX _I32_MAX -#endif // _WIN64 ] - -#define SIG_ATOMIC_MIN INT_MIN -#define SIG_ATOMIC_MAX INT_MAX - -#ifndef SIZE_MAX // [ -# ifdef _WIN64 // [ -# define SIZE_MAX _UI64_MAX -# else // _WIN64 ][ -# define SIZE_MAX _UI32_MAX -# endif // _WIN64 ] -#endif // SIZE_MAX ] - -// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> -#ifndef WCHAR_MIN // [ -# define WCHAR_MIN 0 -#endif // WCHAR_MIN ] -#ifndef WCHAR_MAX // [ -# define WCHAR_MAX _UI16_MAX -#endif // WCHAR_MAX ] - -#define WINT_MIN 0 -#define WINT_MAX _UI16_MAX - -#endif // __STDC_LIMIT_MACROS ] - - -// 7.18.4 Limits of other integer types - -#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 - -// 7.18.4.1 Macros for minimum-width integer constants - -#define INT8_C(val) val##i8 -#define INT16_C(val) val##i16 -#define INT32_C(val) val##i32 -#define INT64_C(val) val##i64 - -#define UINT8_C(val) val##ui8 -#define UINT16_C(val) val##ui16 -#define UINT32_C(val) val##ui32 -#define UINT64_C(val) val##ui64 - -// 7.18.4.2 Macros for greatest-width integer constants -#define INTMAX_C INT64_C -#define UINTMAX_C UINT64_C - -#endif // __STDC_CONSTANT_MACROS ] - - -#endif // _MSC_STDINT_H_ ] +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#if _MSC_VER >= 1600 // [ +#include <stdint.h> +#else // ] _MSC_VER >= 1600 [ + +#include <limits.h> + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C" { +#endif +# include <wchar.h> +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>. +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ]
--- a/Resources/ThirdParty/base64/base64.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/ThirdParty/base64/base64.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,128 +1,128 @@ -/* - base64.cpp and base64.h - - Copyright (C) 2004-2008 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch - -*/ - -#include "base64.h" -#include <string.h> - -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - - -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} - -std::string base64_encode(const std::string& stringToEncode) -{ - const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*> - (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL); - unsigned int in_len = stringToEncode.size(); - - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for(i = 0; (i <4) ; i++) - ret += base64_chars[char_array_4[i]]; - i = 0; - } - } - - if (i) - { - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; - - while((i++ < 3)) - ret += '='; - - } - - return ret; -} - - -std::string base64_decode(const std::string& encoded_string) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++) - char_array_4[i] = base64_chars.find(char_array_4[i]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; - } - } - - if (i) { - for (j = i; j <4; j++) - char_array_4[j] = 0; - - for (j = 0; j <4; j++) - char_array_4[j] = base64_chars.find(char_array_4[j]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - - return ret; -} +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" +#include <string.h> + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(const std::string& stringToEncode) +{ + const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*> + (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL); + unsigned int in_len = stringToEncode.size(); + + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; +} + + +std::string base64_decode(const std::string& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +}
--- a/Resources/ThirdParty/base64/base64.h Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/ThirdParty/base64/base64.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,4 +1,4 @@ -#include <string> - -std::string base64_encode(const std::string& stringToEncode); -std::string base64_decode(const std::string& s); +#include <string> + +std::string base64_encode(const std::string& stringToEncode); +std::string base64_decode(const std::string& s);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/ArithmeticTests/CMakeLists.txt Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,101 @@ +# source ~/Downloads/emsdk-portable/emsdk_env.sh +# cmake .. -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \ +# -DCMAKE_BUILD_TYPE=Debug \ +# -DCMAKE_INSTALL_PREFIX=/tmp/wasm-install/ +# make install +# -> Open the "/tmp/wasm-install/" with Firefox by serving it through Apache +# -> Copy the result as "../arith.h" + + +cmake_minimum_required(VERSION 2.8.3) + + +##################################################################### +## Configuration of the Emscripten compiler for WebAssembly target +##################################################################### + +set(WASM_FLAGS "-s WASM=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + +# Turn on support for debug exceptions +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") + + +##################################################################### +## Prepare DCMTK 3.6.2 +##################################################################### + +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) +include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +set(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.2) +set(DCMTK_URL "http://www.orthanc-server.com/downloads/third-party/dcmtk-3.6.2.tar.gz") +set(DCMTK_MD5 "d219a4152772985191c9b89d75302d12") + +if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}") + set(FirstRun OFF) +else() + set(FirstRun ON) +endif() + +DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}") + +if (FirstRun) + message("Patching file") + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i + ${CMAKE_SOURCE_DIR}/arith.patch + WORKING_DIRECTORY ${DCMTK_SOURCES_DIR}/config/tests + RESULT_VARIABLE Failure + ) + + if (Failure) + message(FATAL_ERROR "Error while patching a file") + endif() +endif() + + +##################################################################### +## Build the DCMTK tests for arithmetics +##################################################################### + +# https://github.com/kripken/emscripten/wiki/WebAssembly#web-server-setup +file(WRITE ${CMAKE_BINARY_DIR}/.htaccess " +AddType application/wasm .wasm +AddOutputFilterByType DEFLATE application/wasm +") + +file(WRITE ${CMAKE_BINARY_DIR}/dcmtk/config/osconfig.h " +#pragma once +#define HAVE_CMATH 1 +#define HAVE_MATH_H 1 +#define HAVE_PROTOTYPE_FINITE 1 +#define HAVE_PROTOTYPE_STD__ISINF 1 +#define HAVE_PROTOTYPE_STD__ISNAN 1 +#define HAVE_STD_NAMESPACE 1 +#define HAVE_STRSTREAM 1 +#define SIZEOF_VOID_P 4 +#define USE_STD_CXX_INCLUDES +") + +include_directories( + ${DCMTK_SOURCES_DIR}/ofstd/include + ${CMAKE_BINARY_DIR} + ) + +add_executable(dcmtk + ${DCMTK_SOURCES_DIR}/config/tests/arith.cc + ${CMAKE_SOURCE_DIR}/Run2.cpp + ) + +install(TARGETS dcmtk DESTINATION .) + +install(FILES + ${CMAKE_BINARY_DIR}/.htaccess + ${CMAKE_BINARY_DIR}/dcmtk.wasm + ${CMAKE_SOURCE_DIR}/app.js + ${CMAKE_SOURCE_DIR}/index.html + DESTINATION . + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/ArithmeticTests/Run2.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,22 @@ +#include <iostream> +#include <emscripten/emscripten.h> + +extern "C" +{ + void EMSCRIPTEN_KEEPALIVE Run2() + { + // This stuff is not properly discovered by DCMTK 3.6.2 configuration scripts + std::cerr << std::endl << std::endl; + std::cerr << "/**" << std::endl; + std::cerr << "#define SIZEOF_CHAR " << sizeof(char) << std::endl; + std::cerr << "#define SIZEOF_DOUBLE " << sizeof(double) << std::endl; + std::cerr << "#define SIZEOF_FLOAT " << sizeof(float) << std::endl; + std::cerr << "#define SIZEOF_INT " << sizeof(int) << std::endl; + std::cerr << "#define SIZEOF_LONG " << sizeof(long) << std::endl; + std::cerr << "#define SIZEOF_SHORT " << sizeof(short) << std::endl; + std::cerr << "#define SIZEOF_VOID_P " << sizeof(void*) << std::endl; + std::cerr << "#define C_CHAR_UNSIGNED " << (!std::is_signed<char>()) << std::endl; + std::cerr << "**/" << std::endl; + std::cerr << std::endl << std::endl; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/ArithmeticTests/app.js Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,34 @@ +function Initialize() +{ + Module.ccall('Run2', // name of C function + null, // return type + [], // argument types + []); + + Module.ccall('Run', // name of C function + 'number', // return type + [], // argument types + []); +} + + +var Module = { + preRun: [], + postRun: [ Initialize ], + print: function(text) { + console.log(text); + }, + printErr: function(text) { + if (text != 'Calling stub instead of signal()') + { + document.getElementById("stderr").textContent += text + '\n'; + } + }, + totalDependencies: 0 +}; + + +if (!('WebAssembly' in window)) { + alert('Sorry, your browser does not support WebAssembly :('); +} else { +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/ArithmeticTests/arith.patch Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,46 @@ +--- /home/jodogne/Subversion/orthanc/Resources/WebAssembly/ArithmeticTests/i/dcmtk-3.6.2/config/tests/arith.cc 2017-07-14 17:41:11.000000000 +0200 ++++ arith.cc 2018-03-28 13:53:34.242234303 +0200 +@@ -19,6 +19,8 @@ + * for being used within oflimits.h. + */ + ++#include <emscripten/emscripten.h> ++ + // Note: This depends on some files of ofstd and osconfig.h, + // although it is part of configure testing itself. + // Therefore, ensure osconfig.h has already been generated +@@ -514,7 +516,9 @@ + } + #endif + +-int main( int argc, char** argv ) ++extern "C" ++{ ++int EMSCRIPTEN_KEEPALIVE Run() + { + #ifdef HAVE_WINDOWS_H + // Activate the fallback workaround, it will only be used +@@ -524,6 +528,8 @@ + #endif + + COUT << "Inspecting fundamental arithmetic types... " << OFendl; ++ ++#if 0 + if( argc != 2 ) + { + STD_NAMESPACE cerr << "-- " << "Error: missing destination file " +@@ -532,6 +538,9 @@ + } + + STD_NAMESPACE ofstream out( argv[1] ); ++#else ++ std::ostream& out = std::cerr; ++#endif + + out << "#ifndef CONFIG_ARITH_H" << '\n'; + out << "#define CONFIG_ARITH_H" << '\n'; +@@ -619,3 +628,4 @@ + + return 0; + } ++}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/ArithmeticTests/index.html Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,13 @@ +<!doctype html> +<html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>DCMTK - Inspect arithmetic types</title> + </head> + <body> + <pre id="stderr"></pre> + <script type="text/javascript" src="app.js"></script> + <script type="text/javascript" async src="dcmtk.js"></script> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/arith.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,70 @@ +/** +#define SIZEOF_CHAR 1 +#define SIZEOF_DOUBLE 8 +#define SIZEOF_FLOAT 4 +#define SIZEOF_INT 4 +#define SIZEOF_LONG 4 +#define SIZEOF_SHORT 2 +#define SIZEOF_VOID_P 4 +#define C_CHAR_UNSIGNED 0 +**/ + + +#ifndef CONFIG_ARITH_H +#define CONFIG_ARITH_H + +#define DCMTK_SIGNED_CHAR_DIGITS10 2 +#define DCMTK_UNSIGNED_CHAR_DIGITS10 2 +#define DCMTK_SIGNED_SHORT_DIGITS10 4 +#define DCMTK_UNSIGNED_SHORT_DIGITS10 4 +#define DCMTK_SIGNED_INT_DIGITS10 9 +#define DCMTK_UNSIGNED_INT_DIGITS10 9 +#define DCMTK_SIGNED_LONG_DIGITS10 9 +#define DCMTK_UNSIGNED_LONG_DIGITS10 9 +#define DCMTK_FLOAT_MAX_DIGITS10 9 +#define DCMTK_DOUBLE_MAX_DIGITS10 17 +#define DCMTK_CHAR_TRAPS OFFalse +#define DCMTK_CHAR_MODULO OFTrue +#define DCMTK_SIGNED_CHAR_TRAPS OFFalse +#define DCMTK_SIGNED_CHAR_MODULO OFTrue +#define DCMTK_UNSIGNED_CHAR_TRAPS OFFalse +#define DCMTK_UNSIGNED_CHAR_MODULO OFTrue +#define DCMTK_SIGNED_SHORT_TRAPS OFFalse +#define DCMTK_SIGNED_SHORT_MODULO OFTrue +#define DCMTK_UNSIGNED_SHORT_TRAPS OFFalse +#define DCMTK_UNSIGNED_SHORT_MODULO OFTrue +#define DCMTK_SIGNED_INT_TRAPS OFFalse +#define DCMTK_SIGNED_INT_MODULO OFTrue +#define DCMTK_UNSIGNED_INT_TRAPS OFFalse +#define DCMTK_UNSIGNED_INT_MODULO OFTrue +#define DCMTK_SIGNED_LONG_TRAPS OFFalse +#define DCMTK_SIGNED_LONG_MODULO OFTrue +#define DCMTK_UNSIGNED_LONG_TRAPS OFFalse +#define DCMTK_UNSIGNED_LONG_MODULO OFTrue +#define DCMTK_FLOAT_TRAPS OFFalse +#define DCMTK_DOUBLE_TRAPS OFFalse +#define DCMTK_FLOAT_HAS_INFINITY OFTrue +#define DCMTK_FLOAT_INFINITY *OFreinterpret_cast( const float*, "\000\000\200\177" ) +#define DCMTK_DOUBLE_HAS_INFINITY OFTrue +#define DCMTK_DOUBLE_INFINITY *OFreinterpret_cast( const double*, "\000\000\000\000\000\000\360\177" ) +#define DCMTK_FLOAT_HAS_QUIET_NAN OFTrue +#define DCMTK_FLOAT_QUIET_NAN *OFreinterpret_cast( const float*, "\000\000\300\177" ) +#define DCMTK_DOUBLE_HAS_QUIET_NAN OFTrue +#define DCMTK_DOUBLE_QUIET_NAN *OFreinterpret_cast( const double*, "\000\000\000\000\000\000\370\177" ) +#define DCMTK_FLOAT_HAS_SIGNALING_NAN OFFalse +#define DCMTK_FLOAT_SIGNALING_NAN *OFreinterpret_cast( const float*, "\001\000\200\177" ) +#define DCMTK_DOUBLE_HAS_SIGNALING_NAN OFFalse +#define DCMTK_DOUBLE_SIGNALING_NAN *OFreinterpret_cast( const double*, "\001\000\000\000\000\000\360\177" ) +#define DCMTK_FLOAT_IS_IEC559 OFFalse +#define DCMTK_DOUBLE_IS_IEC559 OFFalse +#define DCMTK_FLOAT_HAS_DENORM OFdenorm_present +#define DCMTK_FLOAT_DENORM_MIN *OFreinterpret_cast( const float*, "\001\000\000\000" ) +#define DCMTK_DOUBLE_HAS_DENORM OFdenorm_present +#define DCMTK_DOUBLE_DENORM_MIN *OFreinterpret_cast( const double*, "\001\000\000\000\000\000\000\000" ) +#define DCMTK_FLOAT_TINYNESS_BEFORE OFFalse +#define DCMTK_DOUBLE_TINYNESS_BEFORE OFFalse +#define DCMTK_FLOAT_HAS_DENORM_LOSS OFFalse +#define DCMTK_DOUBLE_HAS_DENORM_LOSS OFFalse +#define DCMTK_ROUND_STYLE 1 + +#endif // CONFIG_ARITH_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/dcdict.cc Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,1087 @@ +/* + * + * 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; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/dcdict.h Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,302 @@ +/* + * + * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/Resources/WindowsResources.py Fri Oct 12 15:18:10 2018 +0200 @@ -1,8 +1,9 @@ #!/usr/bin/python # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2018 Osimis S.A., Belgium # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as
--- a/THANKS Thu Oct 29 11:25:45 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -Orthanc - A Lightweight, RESTful DICOM Server -============================================= - -Orthanc has originally been written by Sebastien Jodogne -(cf. "AUTHORS" file). This file contains the list of the people that -have further contributed to Orthanc by reporting problems, suggesting -various improvements, or submitting actual code. Please help keep it -complete and exempt or errors. - - -Contributors ------------- - -* Cyril Paulus <cyril.paulus@student.ulg.ac.be>, for the build process - and suggestions about the REST API. -* Will Ryder <will.ryder@sydney.edu.au>, for improvements with the - handling of series with temporal positions (fMRI and dynamic PET). -* Ryan Walklin <ryanwalklin@gmail.com>, for Mac OS X build. -* Peter Somlo <peter.somlo@gmail.com>, for ClearCanvas and JPEG support. -* 12maksqwe@gmail.com, for fixing issue #8. -* Julien Nabet, for various suggestions to improve the source code. -* Karsten Hilbert <Karsten.Hilbert@gmx.net>, for in-depth testing. -* Marek Swiecicki <mswiecicki@archimedic.pl>, for various suggestions - and sample DICOM files. -* Chris Hafey <chafey@gmail.com>, for suggesting many features and - improvements, for a Windows service+installer with .NET/NSIS. -* Manabu Tokunaga <manabu@lury.net>, for a Windows service with .NET. -* Vincent Kersten <vincent1234567@gmail.com>, for DICOMDIR in the GUI. -* Emsy Chan <emlscs@yahoo.com>, for various contributions - and sample DICOM files. - - -Thanks also to all the contributors active in our Google Group: -https://groups.google.com/forum/#!forum/orthanc-users - - -Debian/Ubuntu -------------- - -* Mathieu Malaterre <mathieu.malaterre@gmail.com>, for sponsoring Orthanc. -* Andreas Tille <andreas@an3as.eu>, for help about packaging. -* Adam Conrad <adconrad@debian.org>, to improve support of big endianness. - - -Fedora and Red Hat ------------------- - -* Mario Ceresa <mrceresa@gmail.com>, for help about packaging. - - -FreeBSD -------- - -* Mikhail <mp39590@gmail.com>, for FreeBSD packaging. - - -Artwork -------- - -https://code.google.com/p/orthanc/wiki/Logos - -* Benjamin Golinvaux <golinvauxb@gmail.com>, for creating the official logo. -* Jean-François Degbomont <jfdegbo@gmail.com>, for submitting a logo. -* Martin Jolly <martin.jolly@gmail.com>, for submitting a logo. -* Philippe Sepers <sepers.philippe@gmail.com>, for submitting a logo.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TODO Fri Oct 12 15:18:10 2018 +0200 @@ -0,0 +1,170 @@ +======================= +=== Orthanc Roadmap === +======================= + +The wishlist from Orthanc users is available on Trello: +https://trello.com/b/gcn33tDM/orthanc-wishlist + + +======= +General +======= + +* Configure an user-defined site UID root if generating DICOM UIDs + ("FromDcmtkBridge::GenerateUuid()") +* Support "/preview" and "/matlab" for LUT color images +* Improve handling of errors in the command queue: + https://groups.google.com/d/msg/orthanc-users/--njEbqcDDI/rBu8XL-Mm-cJ +* Support partial file retrieval in Orthanc::HttpClient +* Support retry counter in Orthanc::HttpClient +* Option to enable DNS lookups in DICOM: https://goo.gl/woa35Z + + +============ +Dependencies +============ + +* Switch from libiconv to libICU (http://site.icu-project.org/download), + as recommended by Boost: + http://www.boost.org/doc/libs/1_64_0/libs/locale/doc/html/building_boost_locale.html + + +============= +Documentation +============= + +* Document REST API with Swagger/OpenAPI + + +======== +REST API +======== + +---------- +Short-term +---------- + +* Create multi-frame images with /tools/create-dicom (by adding a + "MultiFrame" flag to avoid creating a series) +* In the /studies/{id}/anonymize route, add an option to remove secondary captures. + They usually contains Patient info in the image. The SOPClassUID might be used to + identify such secondary captures. + +--------- +Long-term +--------- + +* Stick to the JSONapi or JAREST guidelines for a "v2" of the API: + https://groups.google.com/forum/#!msg/orthanc-users/Bag-SwEE9ZI/-w7QXI6p7-oJ + http://www.admiraalit.nl/jarest/ + + +===== +DICOM +===== + +---------- +Short-term +---------- + +* Support C-GET: + http://dclunie.blogspot.be/2016/05/to-c-move-is-human-to-c-get-divine.html +* Check Big Endian transfer syntax in ParsedDicomFile::EmbedImage and + DicomImageDecoder + +--------- +Long-term +--------- + +* Support DICOM TLS (cf. "--enable-tls" in storescp) +* Support Storage Commitment: + https://groups.google.com/forum/#!msg/orthanc-users/VZOn8St65jw/s8kg_OHesj0J +* Support extended association: + https://groups.google.com/d/msg/orthanc-users/xD4d3mpc6ms/srF7E2goAAAJ + + +======= +Plugins +======= + +--- +SDK +--- + +* 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 + +---------------- +Ideas of plugins +---------------- + +* DICOM-RT primitives (RT-STRUCT, RT-PLAN, RT-DOSE) +* Converter to/from NIfTI +* Decode JPEG2k with grok: https://github.com/GrokImageCompression/grok +* Generate dynamic HTTP content using Lua: + https://groups.google.com/d/msg/orthanc-users/KompazkxRSs/5Rh03mzgDAAJ +* More generally, expose more callbacks of the plugin SDK in Lua: + https://groups.google.com/d/msg/orthanc-users/_FbiRHuXPGM/J-OAv7zaCAAJ +* Authorization plugin for the DICOM protocol: + https://groups.google.com/d/msg/orthanc-users/Wv-QEwTE0IA/rvJxoOjcAQAJ + + +=== +Lua +=== + +* Configure HTTP headers from Lua (in HttpGet(), HttpPost(), + HttpPut(), HttpDelete(), RestApiGet(), RestApiPost(), RestApiPut() + and RestApiDelete(). + https://groups.google.com/forum/#!msg/orthanc-users/WNnW187OILM/6XX_bm96BwAJ + + +=========== +Performance +=========== + + +================ +Code refactoring +================ + +* Use Semaphore::Locker everywhere (instead of explicit + Release() and Acquire()) +* Avoid direct calls to FromDcmtkBridge (make most of its + methods private), go through ParsedDicomFile wherever possible + + +================= +Platform-specific +================= + +--------- +Packaging +--------- + +* CentOS and RHEL through EPEL: + http://fedoraproject.org/wiki/EPEL_Package_Maintainers + +------------------------ +Big-endian architectures +------------------------ + +* Check the generated 16bpp PNG images + +----------------- +Microsoft Windows +----------------- + +* Add compatibility with non-ASCII paths (Orthanc expresses its paths + as UTF-8 strings, but Windows expects them to be translated to the + system locale) + + +===================== +External applications +===================== + +* Create REST bindings with Slicer +* Create REST bindings with Horos/OsiriX
--- a/UnitTestsSources/DicomMapTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/DicomMapTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,10 +34,9 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../Core/Uuid.h" #include "../Core/OrthancException.h" #include "../Core/DicomFormat/DicomMap.h" -#include "../OrthancServer/FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include <memory> @@ -90,7 +90,7 @@ ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME)); ASSERT_FALSE(m.HasTag(0x0010, 0x0010)); - m.SetValue(0x0010, 0x0010, "PatientName"); + m.SetValue(0x0010, 0x0010, "PatientName", false); ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME)); ASSERT_TRUE(m.HasTag(0x0010, 0x0010)); @@ -99,9 +99,9 @@ ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin()); ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID)); - m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID", false); ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); - m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2", false); ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).GetContent()); m.GetTags(s); @@ -117,7 +117,7 @@ std::auto_ptr<DicomMap> mm(m.Clone()); ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).GetContent()); - m.SetValue(DICOM_TAG_PATIENT_ID, "Hello"); + m.SetValue(DICOM_TAG_PATIENT_ID, "Hello", false); ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException); mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID); ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).GetContent()); @@ -151,6 +151,9 @@ static void TestModule(ResourceType level, DicomModule module) { + // REFERENCE: DICOM PS3.3 2015c - Information Object Definitions + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html + std::set<DicomTag> moduleTags, main; DicomTag::AddTagsForModule(moduleTags, module); DicomMap::GetMainDicomTags(main, level); @@ -160,28 +163,49 @@ { bool ok = moduleTags.find(*it) != moduleTags.end(); - // Exceptions for the Series level - /*if ((// - *it == DicomTag(0x, 0x) && - level == ResourceType_Series)) + // Exceptions for the Study level + if (level == ResourceType_Study && + (*it == DicomTag(0x0008, 0x0080) || /* InstitutionName, from Visit identification module, related to Visit */ + *it == DicomTag(0x0032, 0x1032) || /* RequestingPhysician, from Imaging Service Request module, related to Study */ + *it == DicomTag(0x0032, 0x1060))) /* RequestedProcedureDescription, from Requested Procedure module, related to Study */ { ok = true; - }*/ + } + + // Exceptions for the Series level + if (level == ResourceType_Series && + (*it == DicomTag(0x0008, 0x0070) || /* Manufacturer, from General Equipment Module */ + *it == DicomTag(0x0008, 0x1010) || /* StationName, from General Equipment Module */ + *it == DicomTag(0x0018, 0x0024) || /* SequenceName, from MR Image Module (SIMPLIFICATION => Series) */ + *it == DicomTag(0x0018, 0x1090) || /* CardiacNumberOfImages, from MR Image Module (SIMPLIFICATION => Series) */ + *it == DicomTag(0x0020, 0x0037) || /* ImageOrientationPatient, from Image Plane Module (SIMPLIFICATION => Series) */ + *it == DicomTag(0x0020, 0x0105) || /* NumberOfTemporalPositions, from MR Image Module (SIMPLIFICATION => Series) */ + *it == DicomTag(0x0020, 0x1002) || /* ImagesInAcquisition, from General Image Module (SIMPLIFICATION => Series) */ + *it == DicomTag(0x0054, 0x0081) || /* NumberOfSlices, from PET Series module */ + *it == DicomTag(0x0054, 0x0101) || /* NumberOfTimeSlices, from PET Series module */ + *it == DicomTag(0x0054, 0x1000) || /* SeriesType, from PET Series module */ + *it == DicomTag(0x0018, 0x1400) || /* AcquisitionDeviceProcessingDescription, from CR/X-Ray/DX/WholeSlideMicro Image (SIMPLIFICATION => Series) */ + *it == DicomTag(0x0018, 0x0010))) /* ContrastBolusAgent, from Contrast/Bolus module (SIMPLIFICATION => Series) */ + { + ok = true; + } // Exceptions for the Instance level if (level == ResourceType_Instance && - (*it == DicomTag(0x0020, 0x0012) || /* Accession number, from Image module */ - *it == DicomTag(0x0054, 0x1330) || /* Image Index, from PET Image module */ - *it == DicomTag(0x0020, 0x0100) || /* Temporal Position Identifier, from MR Image module */ - *it == DicomTag(0x0028, 0x0008) || /* Number of Frames, from Multi-frame module attributes, related to Image IOD */ - *it == DICOM_TAG_IMAGE_POSITION_PATIENT)) + (*it == DicomTag(0x0020, 0x0012) || /* AccessionNumber, from General Image module */ + *it == DicomTag(0x0054, 0x1330) || /* ImageIndex, from PET Image module */ + *it == DicomTag(0x0020, 0x0100) || /* TemporalPositionIdentifier, from MR Image module */ + *it == DicomTag(0x0028, 0x0008) || /* NumberOfFrames, from Multi-frame module attributes, related to Image */ + *it == DicomTag(0x0020, 0x0032) || /* ImagePositionPatient, from Image Plan module, related to Image */ + *it == DicomTag(0x0020, 0x0037) || /* ImageOrientationPatient, from Image Plane Module (Orthanc 1.4.2) */ + *it == DicomTag(0x0020, 0x4000))) /* ImageComments, from General Image module */ { ok = true; } if (!ok) { - std::cout << it->Format() << ": " << FromDcmtkBridge::GetName(*it) + std::cout << it->Format() << ": " << FromDcmtkBridge::GetTagName(*it, "") << " not expected at level " << EnumerationToString(level) << std::endl; } @@ -194,6 +218,194 @@ { TestModule(ResourceType_Patient, DicomModule_Patient); TestModule(ResourceType_Study, DicomModule_Study); - //TestModule(ResourceType_Series, DicomModule_Series); // TODO + TestModule(ResourceType_Series, DicomModule_Series); // TODO TestModule(ResourceType_Instance, DicomModule_Instance); } + + +TEST(DicomMap, Parse) +{ + DicomMap m; + float f; + double d; + int32_t i; + int64_t j; + uint32_t k; + uint64_t l; + std::string s; + + m.SetValue(DICOM_TAG_PATIENT_NAME, " ", false); // Empty value + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + + m.SetValue(DICOM_TAG_PATIENT_NAME, "0", true); // Binary value + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + + ASSERT_FALSE(m.CopyToString(s, DICOM_TAG_PATIENT_NAME, false)); + ASSERT_TRUE(m.CopyToString(s, DICOM_TAG_PATIENT_NAME, true)); + ASSERT_EQ("0", s); + + + // 2**31-1 + m.SetValue(DICOM_TAG_PATIENT_NAME, "2147483647", false); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + ASSERT_FLOAT_EQ(2147483647.0f, f); + ASSERT_DOUBLE_EQ(2147483647.0, d); + ASSERT_EQ(2147483647, i); + ASSERT_EQ(2147483647ll, j); + ASSERT_EQ(2147483647u, k); + ASSERT_EQ(2147483647ull, l); + + // Test shortcuts + m.SetValue(DICOM_TAG_PATIENT_NAME, "42", false); + ASSERT_TRUE(m.ParseFloat(f, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.ParseDouble(d, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.ParseInteger32(i, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.ParseInteger64(j, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.ParseUnsignedInteger32(k, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.ParseUnsignedInteger64(l, DICOM_TAG_PATIENT_NAME)); + ASSERT_FLOAT_EQ(42.0f, f); + ASSERT_DOUBLE_EQ(42.0, d); + ASSERT_EQ(42, i); + ASSERT_EQ(42ll, j); + ASSERT_EQ(42u, k); + ASSERT_EQ(42ull, l); + + ASSERT_TRUE(m.CopyToString(s, DICOM_TAG_PATIENT_NAME, false)); + ASSERT_EQ("42", s); + ASSERT_TRUE(m.CopyToString(s, DICOM_TAG_PATIENT_NAME, true)); + ASSERT_EQ("42", s); + + + // 2**31 + m.SetValue(DICOM_TAG_PATIENT_NAME, "2147483648", false); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + ASSERT_FLOAT_EQ(2147483648.0f, f); + ASSERT_DOUBLE_EQ(2147483648.0, d); + ASSERT_EQ(2147483648ll, j); + ASSERT_EQ(2147483648u, k); + ASSERT_EQ(2147483648ull, l); + + // 2**32-1 + m.SetValue(DICOM_TAG_PATIENT_NAME, "4294967295", false); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + ASSERT_FLOAT_EQ(4294967295.0f, f); + ASSERT_DOUBLE_EQ(4294967295.0, d); + ASSERT_EQ(4294967295ll, j); + ASSERT_EQ(4294967295u, k); + ASSERT_EQ(4294967295ull, l); + + // 2**32 + m.SetValue(DICOM_TAG_PATIENT_NAME, "4294967296", false); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + ASSERT_FLOAT_EQ(4294967296.0f, f); + ASSERT_DOUBLE_EQ(4294967296.0, d); + ASSERT_EQ(4294967296ll, j); + ASSERT_EQ(4294967296ull, l); + + m.SetValue(DICOM_TAG_PATIENT_NAME, "-1", false); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + ASSERT_FLOAT_EQ(-1.0f, f); + ASSERT_DOUBLE_EQ(-1.0, d); + ASSERT_EQ(-1, i); + ASSERT_EQ(-1ll, j); + + // -2**31 + m.SetValue(DICOM_TAG_PATIENT_NAME, "-2147483648", false); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + ASSERT_FLOAT_EQ(-2147483648.0f, f); + ASSERT_DOUBLE_EQ(-2147483648.0, d); + ASSERT_EQ(static_cast<int32_t>(-2147483648ll), i); + ASSERT_EQ(-2147483648ll, j); + + // -2**31 - 1 + m.SetValue(DICOM_TAG_PATIENT_NAME, "-2147483649", false); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseFloat(f)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseDouble(d)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger32(i)); + ASSERT_TRUE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseInteger64(j)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger32(k)); + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).ParseUnsignedInteger64(l)); + ASSERT_FLOAT_EQ(-2147483649.0f, f); + ASSERT_DOUBLE_EQ(-2147483649.0, d); + ASSERT_EQ(-2147483649ll, j); +} + + +TEST(DicomMap, Serialize) +{ + Json::Value s; + + { + DicomMap m; + m.SetValue(DICOM_TAG_PATIENT_NAME, "Hello", false); + m.SetValue(DICOM_TAG_STUDY_DESCRIPTION, "Binary", true); + m.SetNullValue(DICOM_TAG_SERIES_DESCRIPTION); + m.Serialize(s); + } + + { + DicomMap m; + m.Unserialize(s); + + const DicomValue* v = m.TestAndGetValue(DICOM_TAG_ACCESSION_NUMBER); + ASSERT_TRUE(v == NULL); + + v = m.TestAndGetValue(DICOM_TAG_PATIENT_NAME); + ASSERT_TRUE(v != NULL); + ASSERT_FALSE(v->IsNull()); + ASSERT_FALSE(v->IsBinary()); + ASSERT_EQ("Hello", v->GetContent()); + + v = m.TestAndGetValue(DICOM_TAG_STUDY_DESCRIPTION); + ASSERT_TRUE(v != NULL); + ASSERT_FALSE(v->IsNull()); + ASSERT_TRUE(v->IsBinary()); + ASSERT_EQ("Binary", v->GetContent()); + + v = m.TestAndGetValue(DICOM_TAG_SERIES_DESCRIPTION); + ASSERT_TRUE(v != NULL); + ASSERT_TRUE(v->IsNull()); + ASSERT_FALSE(v->IsBinary()); + ASSERT_THROW(v->GetContent(), OrthancException); + } +}
--- a/UnitTestsSources/FileStorageTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/FileStorageTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,7 +43,6 @@ #include "../Core/Logging.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" -#include "../Core/Uuid.h" #include "../OrthancServer/ServerIndex.h" using namespace Orthanc;
--- a/UnitTestsSources/FromDcmtkTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,24 +34,30 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../OrthancServer/FromDcmtkBridge.h" -#include "../OrthancServer/OrthancInitialization.h" -#include "../OrthancServer/DicomModification.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" +#include "../Core/DicomParsing/ToDcmtkBridge.h" +#include "../Core/DicomParsing/DicomModification.h" #include "../OrthancServer/ServerToolbox.h" #include "../Core/OrthancException.h" #include "../Core/Images/ImageBuffer.h" #include "../Core/Images/PngReader.h" #include "../Core/Images/PngWriter.h" -#include "../Core/Uuid.h" +#include "../Core/Images/Image.h" +#include "../Core/Images/ImageProcessing.h" +#include "../Core/Endianness.h" #include "../Resources/EncodingTests.h" +#include "../Core/DicomNetworking/DicomFindAnswers.h" +#include "../Core/DicomParsing/Internals/DicomImageDecoder.h" +#include "../Plugins/Engine/PluginsEnumerations.h" #include <dcmtk/dcmdata/dcelem.h> +#include <dcmtk/dcmdata/dcdeftag.h> using namespace Orthanc; TEST(DicomFormat, Tag) { - ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); + ASSERT_EQ("PatientName", FromDcmtkBridge::GetTagName(DicomTag(0x0010, 0x0010), "")); DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); ASSERT_EQ(0x0008, t.GetGroup()); @@ -69,21 +76,21 @@ TEST(DicomModification, Basic) { DicomModification m; - m.SetupAnonymization(); + m.SetupAnonymization(DicomVersion_2008); //m.SetLevel(DicomRootLevel_Study); - //m.Replace(DICOM_TAG_PATIENT_ID, "coucou"); - //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); + //m.ReplacePlainString(DICOM_TAG_PATIENT_ID, "coucou"); + //m.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou"); - ParsedDicomFile o; + ParsedDicomFile o(true); o.SaveToFile("UnitTestsResults/anon.dcm"); for (int i = 0; i < 10; i++) { char b[1024]; sprintf(b, "UnitTestsResults/anon%06d.dcm", i); - std::auto_ptr<ParsedDicomFile> f(o.Clone()); + std::auto_ptr<ParsedDicomFile> f(o.Clone(false)); if (i > 4) - o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); + o.ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); m.Apply(*f); f->SaveToFile(b); } @@ -96,28 +103,28 @@ const DicomTag privateTag(0x0045, 0x0010); const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020")); - ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag)); - ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag2)); + ASSERT_TRUE(privateTag.IsPrivate()); + ASSERT_TRUE(privateTag2.IsPrivate()); ASSERT_EQ(0x0031, privateTag2.GetGroup()); ASSERT_EQ(0x1020, privateTag2.GetElement()); std::string s; - ParsedDicomFile o; - o.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); + ParsedDicomFile o(true); + o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou"); ASSERT_FALSE(o.GetTagValue(s, privateTag)); o.Insert(privateTag, "private tag", false); ASSERT_TRUE(o.GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - ASSERT_THROW(o.Replace(privateTag2, "hello", DicomReplaceMode_ThrowIfAbsent), OrthancException); + ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent), OrthancException); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, "hello", DicomReplaceMode_IgnoreIfAbsent); + o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, "hello", DicomReplaceMode_InsertIfAbsent); + o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent); ASSERT_TRUE(o.GetTagValue(s, privateTag2)); ASSERT_STREQ("hello", s.c_str()); - o.Replace(privateTag2, "hello world"); + o.ReplacePlainString(privateTag2, "hello world"); ASSERT_TRUE(o.GetTagValue(s, privateTag2)); ASSERT_STREQ("hello world", s.c_str()); @@ -125,7 +132,7 @@ ASSERT_FALSE(Toolbox::IsUuid(s)); DicomModification m; - m.SetupAnonymization(); + m.SetupAnonymization(DicomVersion_2008); m.Keep(privateTag); m.Apply(o); @@ -135,7 +142,7 @@ ASSERT_TRUE(o.GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); - m.SetupAnonymization(); + m.SetupAnonymization(DicomVersion_2008); m.Apply(o); ASSERT_FALSE(o.GetTagValue(s, privateTag)); } @@ -149,7 +156,7 @@ std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; std::string m, cc; - Toolbox::DecodeDataUriScheme(m, cc, s); + ASSERT_TRUE(Toolbox::DecodeDataUriScheme(m, cc, s)); ASSERT_EQ("image/png", m); @@ -160,7 +167,7 @@ ASSERT_EQ(5u, reader.GetWidth()); ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); - ParsedDicomFile o; + ParsedDicomFile o(true); o.EmbedContent(s); o.SaveToFile("UnitTestsResults/png1.dcm"); @@ -172,7 +179,7 @@ // Check box in Graylevel8 s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="; o.EmbedContent(s); - //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); + //o.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); o.SaveToFile("UnitTestsResults/png3.dcm"); @@ -184,17 +191,20 @@ img.SetHeight(256); img.SetFormat(PixelFormat_Grayscale16); + ImageAccessor accessor; + img.GetWriteableAccessor(accessor); + uint16_t v = 0; for (unsigned int y = 0; y < img.GetHeight(); y++) { - uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y)); + uint16_t *p = reinterpret_cast<uint16_t*>(accessor.GetRow(y)); for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++) { *p = v; } } - o.EmbedImage(img.GetAccessor()); + o.EmbedImage(accessor); o.SaveToFile("UnitTestsResults/png4.dcm"); } } @@ -215,12 +225,13 @@ TEST(FromDcmtkBridge, Enumerations) { + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2 Encoding e; ASSERT_FALSE(GetDicomEncoding(e, "")); - ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 6")); ASSERT_EQ(Encoding_Utf8, e); + ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 6")); ASSERT_EQ(Encoding_Ascii, e); - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ - Table C.12-2 + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-2 ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 100")); ASSERT_EQ(Encoding_Latin1, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 101")); ASSERT_EQ(Encoding_Latin2, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 109")); ASSERT_EQ(Encoding_Latin3, e); @@ -230,11 +241,11 @@ ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 126")); ASSERT_EQ(Encoding_Greek, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 138")); ASSERT_EQ(Encoding_Hebrew, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 148")); ASSERT_EQ(Encoding_Latin5, e); - ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 13")); ASSERT_EQ(Encoding_Japanese, e); + ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 13")); ASSERT_EQ(Encoding_Japanese, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 166")); ASSERT_EQ(Encoding_Thai, e); - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ - Table C.12-3 - ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 6")); ASSERT_EQ(Encoding_Utf8, e); + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-3 + ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 6")); ASSERT_EQ(Encoding_Ascii, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 100")); ASSERT_EQ(Encoding_Latin1, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 101")); ASSERT_EQ(Encoding_Latin2, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 109")); ASSERT_EQ(Encoding_Latin3, e); @@ -244,17 +255,18 @@ ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 126")); ASSERT_EQ(Encoding_Greek, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 138")); ASSERT_EQ(Encoding_Hebrew, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 148")); ASSERT_EQ(Encoding_Latin5, e); - ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 13")); ASSERT_EQ(Encoding_Japanese, e); + ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 13")); ASSERT_EQ(Encoding_Japanese, e); ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 166")); ASSERT_EQ(Encoding_Thai, e); - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ - Table C.12-4 - ASSERT_FALSE(GetDicomEncoding(e, "ISO 2022 IR 87")); //ASSERT_EQ(Encoding_JapaneseKanji, e); + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-4 + ASSERT_FALSE(GetDicomEncoding(e, "ISO 2022 IR 87")); //ASSERT_EQ(Encoding_JapaneseKanji, e); ASSERT_FALSE(GetDicomEncoding(e, "ISO 2022 IR 159")); //ASSERT_EQ(Encoding_JapaneseKanjiSupplementary, e); ASSERT_FALSE(GetDicomEncoding(e, "ISO 2022 IR 149")); //ASSERT_EQ(Encoding_Korean, e); - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ - Table C.12-5 + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-5 ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 192")); ASSERT_EQ(Encoding_Utf8, e); - ASSERT_TRUE(GetDicomEncoding(e, "GB18030")); ASSERT_EQ(Encoding_Chinese, e); + ASSERT_TRUE(GetDicomEncoding(e, "GB18030")); ASSERT_EQ(Encoding_Chinese, e); + ASSERT_TRUE(GetDicomEncoding(e, "GBK")); ASSERT_EQ(Encoding_Chinese, e); } @@ -266,7 +278,7 @@ std::string dicom; { - ParsedDicomFile f; + ParsedDicomFile f(true); f.SetEncoding(testEncodings[i]); std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]); @@ -293,16 +305,59 @@ TEST(FromDcmtkBridge, ValueRepresentation) { - ASSERT_EQ(ValueRepresentation_PatientName, - FromDcmtkBridge::GetValueRepresentation(DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ(ValueRepresentation_PersonName, + FromDcmtkBridge::LookupValueRepresentation(DICOM_TAG_PATIENT_NAME)); ASSERT_EQ(ValueRepresentation_Date, - FromDcmtkBridge::GetValueRepresentation(DicomTag(0x0008, 0x0020) /* StudyDate */)); + FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x0020) /* StudyDate */)); ASSERT_EQ(ValueRepresentation_Time, - FromDcmtkBridge::GetValueRepresentation(DicomTag(0x0008, 0x0030) /* StudyTime */)); + FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x0030) /* StudyTime */)); ASSERT_EQ(ValueRepresentation_DateTime, - FromDcmtkBridge::GetValueRepresentation(DicomTag(0x0008, 0x002a) /* AcquisitionDateTime */)); - ASSERT_EQ(ValueRepresentation_Other, - FromDcmtkBridge::GetValueRepresentation(DICOM_TAG_PATIENT_ID)); + FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x002a) /* AcquisitionDateTime */)); + ASSERT_EQ(ValueRepresentation_NotSupported, + FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0001, 0x0001) /* some private tag */)); +} + + +TEST(FromDcmtkBridge, ValueRepresentationConversions) +{ + ASSERT_EQ(1, ValueRepresentation_ApplicationEntity); + ASSERT_EQ(1, OrthancPluginValueRepresentation_AE); + + for (int i = ValueRepresentation_ApplicationEntity; + i <= ValueRepresentation_NotSupported; i++) + { + ValueRepresentation vr = static_cast<ValueRepresentation>(i); + + if (vr == ValueRepresentation_NotSupported) + { + ASSERT_THROW(ToDcmtkBridge::Convert(vr), OrthancException); + ASSERT_THROW(Plugins::Convert(vr), OrthancException); + } + else if (vr == ValueRepresentation_OtherDouble || + vr == ValueRepresentation_OtherLong || + vr == ValueRepresentation_UniversalResource || + vr == ValueRepresentation_UnlimitedCharacters) + { + // These VR are not supported as of DCMTK 3.6.0 + ASSERT_THROW(ToDcmtkBridge::Convert(vr), OrthancException); + ASSERT_EQ(OrthancPluginValueRepresentation_UN, Plugins::Convert(vr)); + } + else + { + ASSERT_EQ(vr, FromDcmtkBridge::Convert(ToDcmtkBridge::Convert(vr))); + + OrthancPluginValueRepresentation plugins = Plugins::Convert(vr); + ASSERT_EQ(vr, Plugins::Convert(plugins)); + } + } + + for (int i = OrthancPluginValueRepresentation_AE; + i <= OrthancPluginValueRepresentation_UT; i++) + { + OrthancPluginValueRepresentation plugins = static_cast<OrthancPluginValueRepresentation>(i); + ValueRepresentation orthanc = Plugins::Convert(plugins); + ASSERT_EQ(plugins, Plugins::Convert(orthanc)); + } } @@ -329,102 +384,135 @@ } -TEST(FromDcmtkBridge, FromJson) +namespace Orthanc { - std::auto_ptr<DcmElement> element; - - { - Json::Value a; - a = "Hello"; - element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)); - - Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii); - ASSERT_EQ("Hello", b["0010,0010"].asString()); - } - + // Namespace for the "FRIEND_TEST()" directive in "FromDcmtkBridge" to apply: + // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#private-class-members + TEST(FromDcmtkBridge, FromJson) { - 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); - } - - { - 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); - } - - { - 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)); - - Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii); - ASSERT_EQ("Hello", b["0010,0010"].asString()); - } - - { - Json::Value a = Json::arrayValue; - CreateSampleJson(a); - element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8)); + std::auto_ptr<DcmElement> element; { + Json::Value a; + a = "Hello"; + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)); + Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii); - ASSERT_EQ(Json::arrayValue, b["0008,1110"].type()); - ASSERT_EQ(2, b["0008,1110"].size()); - - Json::Value::ArrayIndex i = (b["0008,1110"][0]["0010,0010"].asString() == "Hello") ? 0 : 1; + std::set<DicomTag> ignoreTagLength; + ignoreTagLength.insert(DICOM_TAG_PATIENT_ID); + + FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, + DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + ASSERT_TRUE(b.isMember("0010,0010")); + ASSERT_EQ("Hello", b["0010,0010"].asString()); + + FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, + DicomToJsonFlags_Default, 3, Encoding_Ascii, ignoreTagLength); + ASSERT_TRUE(b["0010,0010"].isNull()); // "Hello" has more than 3 characters - ASSERT_EQ(3, b["0008,1110"][i].size()); - ASSERT_EQ(2, b["0008,1110"][1 - i].size()); - ASSERT_EQ(b["0008,1110"][i]["0010,0010"].asString(), "Hello"); - ASSERT_EQ(b["0008,1110"][i]["0010,0020"].asString(), "World"); - ASSERT_EQ(b["0008,1110"][i]["0008,1030"].asString(), "Toto"); - ASSERT_EQ(b["0008,1110"][1 - i]["0010,0010"].asString(), "Hello2"); - ASSERT_EQ(b["0008,1110"][1 - i]["0010,0020"].asString(), "World2"); + FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Full, + DicomToJsonFlags_Default, 3, Encoding_Ascii, ignoreTagLength); + ASSERT_TRUE(b["0010,0010"].isObject()); + ASSERT_EQ("PatientName", b["0010,0010"]["Name"].asString()); + ASSERT_EQ("TooLong", b["0010,0010"]["Type"].asString()); + ASSERT_TRUE(b["0010,0010"]["Value"].isNull()); + + ignoreTagLength.insert(DICOM_TAG_PATIENT_NAME); + FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, + DicomToJsonFlags_Default, 3, Encoding_Ascii, ignoreTagLength); + ASSERT_EQ("Hello", b["0010,0010"].asString()); + } + + { + 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); } { + 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); + } + + { + 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)); + Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0, Encoding_Ascii); + std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, + DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + ASSERT_EQ("Hello", b["0010,0010"].asString()); + } + + { + Json::Value a = Json::arrayValue; + CreateSampleJson(a); + element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8)); - Json::Value c; - Toolbox::SimplifyTags(c, b); + { + Json::Value b; + std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, + DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + ASSERT_EQ(Json::arrayValue, b["0008,1110"].type()); + ASSERT_EQ(2u, b["0008,1110"].size()); + + Json::Value::ArrayIndex i = (b["0008,1110"][0]["0010,0010"].asString() == "Hello") ? 0 : 1; - a[1]["PatientName"] = "Hello2"; // To remove the Data URI Scheme encoding - ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a)); + ASSERT_EQ(3u, b["0008,1110"][i].size()); + ASSERT_EQ(2u, b["0008,1110"][1 - i].size()); + ASSERT_EQ(b["0008,1110"][i]["0010,0010"].asString(), "Hello"); + ASSERT_EQ(b["0008,1110"][i]["0010,0020"].asString(), "World"); + ASSERT_EQ(b["0008,1110"][i]["0008,1030"].asString(), "Toto"); + ASSERT_EQ(b["0008,1110"][1 - i]["0010,0010"].asString(), "Hello2"); + ASSERT_EQ(b["0008,1110"][1 - i]["0010,0020"].asString(), "World2"); + } + + { + Json::Value b; + std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Full, + DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + + Json::Value c; + ServerToolbox::SimplifyTags(c, b, DicomToJsonFormat_Human); + + a[1]["PatientName"] = "Hello2"; // To remove the Data URI Scheme encoding + ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a)); + } } } } - TEST(ParsedDicomFile, InsertReplaceStrings) { - ParsedDicomFile f; + 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.Replace(DICOM_TAG_SOP_INSTANCE_UID, "Toto"); // (*) - f.Replace(DICOM_TAG_SOP_CLASS_UID, "Tata"); // (**) + f.ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, "Toto"); // (*) + f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "Tata"); // (**) std::string s; + ASSERT_FALSE(f.LookupTransferSyntax(s)); - ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_ThrowIfAbsent), OrthancException); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_IgnoreIfAbsent); + 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); ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", 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, "Accession2", 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, "Accession3", 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"); @@ -445,7 +533,7 @@ TEST(ParsedDicomFile, InsertReplaceJson) { - ParsedDicomFile f; + ParsedDicomFile f(true); Json::Value a; CreateSampleJson(a); @@ -470,18 +558,18 @@ { Json::Value b; - f.ToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0); + f.DatasetToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0); Json::Value c; - Toolbox::SimplifyTags(c, b); + ServerToolbox::SimplifyTags(c, b, DicomToJsonFormat_Human); ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a)); ASSERT_NE(0, c["ReferencedStudySequence"].compare(a)); // Because Data URI Scheme decoding was enabled } a = "data:application/octet-stream;base64,VGF0YQ=="; // echo -n "Tata" | base64 - f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false); // (*) - f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true); // (**) + 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)); @@ -497,7 +585,7 @@ TEST(ParsedDicomFile, JsonEncoding) { - ParsedDicomFile f; + ParsedDicomFile f(true); for (unsigned int i = 0; i < testEncodingsCount; i++) { @@ -512,10 +600,10 @@ } Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]); - f.Replace(DICOM_TAG_PATIENT_NAME, s, false); + f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent); Json::Value v; - f.ToJson(v, DicomToJsonFormat_Simple, DicomToJsonFlags_Default, 0); + f.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i])); } } @@ -524,85 +612,705 @@ TEST(ParsedDicomFile, ToJsonFlags1) { - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), EVR_PN, "MyPrivateTag", 1, 1); - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), EVR_PN, "Declared public tag", 1, 1); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_PersonName, "MyPrivateTag", 1, 1, ""); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, ""); - ParsedDicomFile f; + 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 Json::Value v; - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); ASSERT_FALSE(v.isMember("7053,1000")); ASSERT_TRUE(v.isMember("7050,1000")); ASSERT_EQ(Json::stringValue, v["7050,1000"].type()); ASSERT_EQ("Some public tag", v["7050,1000"].asString()); - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(7, v.getMemberNames().size()); + ASSERT_EQ(7u, v.getMemberNames().size()); + ASSERT_FALSE(v.isMember("7052,1000")); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_TRUE(v.isMember("7053,1000")); + 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); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); ASSERT_TRUE(v.isMember("7050,1000")); ASSERT_TRUE(v.isMember("7053,1000")); ASSERT_EQ("Some public tag", v["7050,1000"].asString()); - ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); // TODO SHOULD BE STRING + std::string mime, content; + ASSERT_EQ(Json::stringValue, v["7053,1000"].type()); + ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7053,1000"].asString())); + ASSERT_EQ("application/octet-stream", mime); + ASSERT_EQ("Some private tag", content); - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludeUnknownTags, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(7, v.getMemberNames().size()); + ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); ASSERT_TRUE(v.isMember("7052,1000")); ASSERT_FALSE(v.isMember("7053,1000")); ASSERT_EQ("Some public tag", v["7050,1000"].asString()); - ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); // TODO SHOULD BE STRING + ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); - f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags), 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(8, v.getMemberNames().size()); + ASSERT_EQ(7u, v.getMemberNames().size()); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_TRUE(v.isMember("7052,1000")); + ASSERT_FALSE(v.isMember("7053,1000")); + ASSERT_EQ("Some public tag", v["7050,1000"].asString()); + ASSERT_EQ(Json::stringValue, v["7052,1000"].type()); + ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7052,1000"].asString())); + 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); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(8u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); ASSERT_TRUE(v.isMember("7052,1000")); ASSERT_TRUE(v.isMember("7053,1000")); ASSERT_EQ("Some public tag", v["7050,1000"].asString()); - ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); // TODO SHOULD BE STRING - ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); // TODO SHOULD BE STRING + ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); + ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); } TEST(ParsedDicomFile, ToJsonFlags2) { - ParsedDicomFile f; + ParsedDicomFile f(true); f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false); Json::Value v; - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(5, v.getMemberNames().size()); + ASSERT_EQ(5u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7fe0,0010")); - f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7fe0,0010")); ASSERT_EQ(Json::nullValue, v["7fe0,0010"].type()); - f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7fe0,0010")); ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type()); ASSERT_EQ("Pixels", v["7fe0,0010"].asString()); - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0); ASSERT_EQ(Json::objectValue, v.type()); - ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7fe0,0010")); ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type()); std::string mime, content; - Toolbox::DecodeDataUriScheme(mime, content, v["7fe0,0010"].asString()); + ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7fe0,0010"].asString())); ASSERT_EQ("application/octet-stream", mime); ASSERT_EQ("Pixels", content); } + + +TEST(DicomFindAnswers, Basic) +{ + DicomFindAnswers a(false); + + { + DicomMap m; + m.SetValue(DICOM_TAG_PATIENT_ID, "hello", false); + a.Add(m); + } + + { + ParsedDicomFile d(true); + d.ReplacePlainString(DICOM_TAG_PATIENT_ID, "my"); + a.Add(d); + } + + { + DicomMap m; + m.SetValue(DICOM_TAG_PATIENT_ID, "world", false); + a.Add(m); + } + + Json::Value j; + a.ToJson(j, true); + ASSERT_EQ(3u, j.size()); + + //std::cout << j; +} + + +TEST(ParsedDicomFile, FromJson) +{ + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7057, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag2", 1, 1, "ORTHANC"); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7059, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag3", 1, 1, ""); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag2", 1, 1, ""); + + Json::Value v; + const std::string sopClassUid = "1.2.840.10008.5.1.4.1.1.1"; // CR Image Storage: + + // Test the private creator + ASSERT_EQ(DcmTag_ERROR_TagName, FromDcmtkBridge::GetTagName(DicomTag(0x7057, 0x1000), "NOPE")); + ASSERT_EQ("MyPrivateTag2", FromDcmtkBridge::GetTagName(DicomTag(0x7057, 0x1000), "ORTHANC")); + + { + v["SOPClassUID"] = sopClassUid; + v["SpecificCharacterSet"] = "ISO_IR 148"; // This is latin-5 + v["PatientName"] = "Sébastien"; + v["7050-1000"] = "Some public tag"; // Even group => public tag + v["7052-1000"] = "Some unknown tag"; // Even group => public, unknown tag + v["7057-1000"] = "Some private tag"; // Odd group => private tag + v["7059-1000"] = "Some private tag2"; // Odd group => private tag, with an odd length to test padding + + std::string s; + Toolbox::EncodeDataUriScheme(s, "application/octet-stream", "Sebastien"); + v["StudyDescription"] = s; + + v["PixelData"] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // A red dot of 5x5 pixels + v["0040,0100"] = Json::arrayValue; // ScheduledProcedureStepSequence + + Json::Value vv; + vv["Modality"] = "MR"; + v["0040,0100"].append(vv); + + vv["Modality"] = "CT"; + v["0040,0100"].append(vv); + } + + const DicomToJsonFlags toJsonFlags = static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeBinary | + DicomToJsonFlags_IncludePixelData | + DicomToJsonFlags_IncludePrivateTags | + DicomToJsonFlags_IncludeUnknownTags | + DicomToJsonFlags_ConvertBinaryToAscii); + + + { + std::auto_ptr<ParsedDicomFile> dicom + (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers))); + + Json::Value vv; + dicom->DatasetToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0); + + ASSERT_EQ(vv["SOPClassUID"].asString(), sopClassUid); + ASSERT_EQ(vv["MediaStorageSOPClassUID"].asString(), sopClassUid); + ASSERT_TRUE(vv.isMember("SOPInstanceUID")); + ASSERT_TRUE(vv.isMember("SeriesInstanceUID")); + ASSERT_TRUE(vv.isMember("StudyInstanceUID")); + ASSERT_TRUE(vv.isMember("PatientID")); + } + + + { + std::auto_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); + + std::string mime, content; + ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, vv["PixelData"].asString())); + ASSERT_EQ("application/octet-stream", mime); + ASSERT_EQ(5u * 5u * 3u /* the red dot is 5x5 pixels in RGB24 */ + 1 /* for padding */, content.size()); + } + + + { + std::auto_ptr<ParsedDicomFile> dicom + (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme))); + + Json::Value vv; + dicom->DatasetToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0); + + ASSERT_FALSE(vv.isMember("SOPInstanceUID")); + ASSERT_FALSE(vv.isMember("SeriesInstanceUID")); + ASSERT_FALSE(vv.isMember("StudyInstanceUID")); + ASSERT_FALSE(vv.isMember("PatientID")); + ASSERT_EQ(2u, vv["0040,0100"].size()); + ASSERT_EQ("MR", vv["0040,0100"][0]["0008,0060"].asString()); + ASSERT_EQ("CT", vv["0040,0100"][1]["0008,0060"].asString()); + ASSERT_EQ("Some public tag", vv["7050,1000"].asString()); + ASSERT_EQ("Some unknown tag", vv["7052,1000"].asString()); + ASSERT_EQ("Some private tag", vv["7057,1000"].asString()); + ASSERT_EQ("Some private tag2", vv["7059,1000"].asString()); + ASSERT_EQ("Sébastien", vv["0010,0010"].asString()); + ASSERT_EQ("Sebastien", vv["0008,1030"].asString()); + ASSERT_EQ("ISO_IR 148", vv["0008,0005"].asString()); + ASSERT_EQ("5", vv[DICOM_TAG_ROWS.Format()].asString()); + ASSERT_EQ("5", vv[DICOM_TAG_COLUMNS.Format()].asString()); + ASSERT_TRUE(vv[DICOM_TAG_PIXEL_DATA.Format()].asString().empty()); + } +} + + + +TEST(TestImages, PatternGrayscale8) +{ + static const char* PATH = "UnitTestsResults/PatternGrayscale8.dcm"; + + Orthanc::Image image(Orthanc::PixelFormat_Grayscale8, 256, 256, false); + + for (int y = 0; y < 256; y++) + { + uint8_t *p = reinterpret_cast<uint8_t*>(image.GetRow(y)); + for (int x = 0; x < 256; x++, p++) + { + *p = y; + } + } + + Orthanc::ImageAccessor r; + + image.GetRegion(r, 32, 32, 64, 192); + Orthanc::ImageProcessing::Set(r, 0); + + image.GetRegion(r, 160, 32, 64, 192); + Orthanc::ImageProcessing::Set(r, 255); + + { + ParsedDicomFile f(true); + f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7"); + f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998"); + f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC"); + f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc"); + f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns"); + f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale8"); + f.EmbedImage(image); + + f.SaveToFile(PATH); + } + + { + std::string s; + Orthanc::SystemToolbox::ReadFile(s, PATH); + Orthanc::ParsedDicomFile f(s); + + std::auto_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()); + + for (int y = 0; y < 256; y++) + { + const void* a = image.GetConstRow(y); + const void* b = decoded->GetConstRow(y); + ASSERT_EQ(0, memcmp(a, b, 256)); + } + } +} + + +TEST(TestImages, PatternRGB) +{ + static const char* PATH = "UnitTestsResults/PatternRGB24.dcm"; + + Orthanc::Image image(Orthanc::PixelFormat_RGB24, 384, 256, false); + + for (int y = 0; y < 256; y++) + { + uint8_t *p = reinterpret_cast<uint8_t*>(image.GetRow(y)); + for (int x = 0; x < 128; x++, p += 3) + { + p[0] = y; + p[1] = 0; + p[2] = 0; + } + for (int x = 128; x < 128 * 2; x++, p += 3) + { + p[0] = 0; + p[1] = 255 - y; + p[2] = 0; + } + for (int x = 128 * 2; x < 128 * 3; x++, p += 3) + { + p[0] = 0; + p[1] = 0; + p[2] = y; + } + } + + { + ParsedDicomFile f(true); + f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7"); + f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998"); + f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC"); + f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc"); + f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns"); + f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "RGB24"); + f.EmbedImage(image); + + f.SaveToFile(PATH); + } + + { + std::string s; + Orthanc::SystemToolbox::ReadFile(s, PATH); + Orthanc::ParsedDicomFile f(s); + + std::auto_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()); + + for (int y = 0; y < 256; y++) + { + const void* a = image.GetConstRow(y); + const void* b = decoded->GetConstRow(y); + ASSERT_EQ(0, memcmp(a, b, 3 * 384)); + } + } +} + + +TEST(TestImages, PatternUint16) +{ + static const char* PATH = "UnitTestsResults/PatternGrayscale16.dcm"; + + Orthanc::Image image(Orthanc::PixelFormat_Grayscale16, 256, 256, false); + + uint16_t v = 0; + for (int y = 0; y < 256; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(image.GetRow(y)); + for (int x = 0; x < 256; x++, v++, p++) + { + *p = htole16(v); // Orthanc uses Little-Endian transfer syntax to encode images + } + } + + Orthanc::ImageAccessor r; + + image.GetRegion(r, 32, 32, 64, 192); + Orthanc::ImageProcessing::Set(r, 0); + + image.GetRegion(r, 160, 32, 64, 192); + Orthanc::ImageProcessing::Set(r, 65535); + + { + ParsedDicomFile f(true); + f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7"); + f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998"); + f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC"); + f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc"); + f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns"); + f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale16"); + f.EmbedImage(image); + + f.SaveToFile(PATH); + } + + { + std::string s; + Orthanc::SystemToolbox::ReadFile(s, PATH); + Orthanc::ParsedDicomFile f(s); + + std::auto_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()); + + for (int y = 0; y < 256; y++) + { + const void* a = image.GetConstRow(y); + const void* b = decoded->GetConstRow(y); + ASSERT_EQ(0, memcmp(a, b, 512)); + } + } +} + + +TEST(TestImages, PatternInt16) +{ + static const char* PATH = "UnitTestsResults/PatternSignedGrayscale16.dcm"; + + Orthanc::Image image(Orthanc::PixelFormat_SignedGrayscale16, 256, 256, false); + + int16_t v = -32768; + for (int y = 0; y < 256; y++) + { + int16_t *p = reinterpret_cast<int16_t*>(image.GetRow(y)); + for (int x = 0; x < 256; x++, v++, p++) + { + *p = htole16(v); // Orthanc uses Little-Endian transfer syntax to encode images + } + } + + Orthanc::ImageAccessor r; + image.GetRegion(r, 32, 32, 64, 192); + Orthanc::ImageProcessing::Set(r, -32768); + + image.GetRegion(r, 160, 32, 64, 192); + Orthanc::ImageProcessing::Set(r, 32767); + + { + ParsedDicomFile f(true); + f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7"); + f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998"); + f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC"); + f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc"); + f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns"); + f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "SignedGrayscale16"); + f.EmbedImage(image); + + f.SaveToFile(PATH); + } + + { + std::string s; + Orthanc::SystemToolbox::ReadFile(s, PATH); + Orthanc::ParsedDicomFile f(s); + + std::auto_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()); + + for (int y = 0; y < 256; y++) + { + const void* a = image.GetConstRow(y); + const void* b = decoded->GetConstRow(y); + ASSERT_EQ(0, memcmp(a, b, 512)); + } + } +} + + + +static void CheckEncoding(const ParsedDicomFile& dicom, + Encoding expected) +{ + const char* value = NULL; + ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_SpecificCharacterSet, value).good()); + + Encoding encoding; + ASSERT_TRUE(GetDicomEncoding(encoding, value)); + ASSERT_EQ(expected, encoding); +} + + +TEST(ParsedDicomFile, DicomMapEncodings1) +{ + SetDefaultDicomEncoding(Encoding_Ascii); + ASSERT_EQ(Encoding_Ascii, GetDefaultDicomEncoding()); + + { + DicomMap m; + ParsedDicomFile dicom(m); + ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card()); + CheckEncoding(dicom, Encoding_Ascii); + } + + { + DicomMap m; + ParsedDicomFile dicom(m, Encoding_Latin4); + ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card()); + CheckEncoding(dicom, Encoding_Latin4); + } + + { + DicomMap m; + m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 148", false); + ParsedDicomFile dicom(m); + ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card()); + CheckEncoding(dicom, Encoding_Latin5); + } + + { + DicomMap m; + m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 148", false); + ParsedDicomFile dicom(m, Encoding_Latin1); + ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card()); + CheckEncoding(dicom, Encoding_Latin5); + } +} + + +TEST(ParsedDicomFile, DicomMapEncodings2) +{ + const char* utf8 = NULL; + for (unsigned int i = 0; i < testEncodingsCount; i++) + { + if (testEncodings[i] == Encoding_Utf8) + { + utf8 = testEncodingsEncoded[i]; + break; + } + } + + ASSERT_TRUE(utf8 != NULL); + + for (unsigned int i = 0; i < testEncodingsCount; i++) + { + // 1251 codepage is not supported by the core DICOM standard, ignore it + if (testEncodings[i] != Encoding_Windows1251) + { + { + // Sanity check to test the proper behavior of "EncodingTests.py" + std::string encoded = Toolbox::ConvertFromUtf8(testEncodingsExpected[i], testEncodings[i]); + ASSERT_STREQ(testEncodingsEncoded[i], encoded.c_str()); + std::string decoded = Toolbox::ConvertToUtf8(encoded, testEncodings[i]); + ASSERT_STREQ(testEncodingsExpected[i], decoded.c_str()); + + if (testEncodings[i] != Encoding_Chinese) + { + // A specific source string is used in "EncodingTests.py" to + // test against Chinese, it is normal that it does not correspond to UTF8 + + std::string encoded = Toolbox::ConvertToUtf8(Toolbox::ConvertFromUtf8(utf8, testEncodings[i]), testEncodings[i]); + ASSERT_STREQ(testEncodingsExpected[i], encoded.c_str()); + } + } + + + Json::Value v; + + { + DicomMap m; + m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false); + + ParsedDicomFile dicom(m, testEncodings[i]); + + const char* encoded = NULL; + ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_PatientName, encoded).good()); + ASSERT_STREQ(testEncodingsEncoded[i], encoded); + + dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); + + Encoding encoding; + ASSERT_TRUE(GetDicomEncoding(encoding, v["SpecificCharacterSet"].asCString())); + ASSERT_EQ(encoding, testEncodings[i]); + ASSERT_STREQ(testEncodingsExpected[i], v["PatientName"].asCString()); + } + + + { + DicomMap m; + m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(testEncodings[i]), false); + m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false); + + ParsedDicomFile dicom(m, testEncodings[i]); + + Json::Value v2; + dicom.DatasetToJson(v2, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); + + ASSERT_EQ(v2["PatientName"].asString(), v["PatientName"].asString()); + ASSERT_EQ(v2["SpecificCharacterSet"].asString(), v["SpecificCharacterSet"].asString()); + } + } + } +} + + +TEST(ParsedDicomFile, ChangeEncoding) +{ + for (unsigned int i = 0; i < testEncodingsCount; i++) + { + // 1251 codepage is not supported by the core DICOM standard, ignore it + if (testEncodings[i] != Encoding_Windows1251) + { + DicomMap m; + m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false); + + std::string tag; + + ParsedDicomFile dicom(m, Encoding_Utf8); + ASSERT_EQ(Encoding_Utf8, dicom.GetEncoding()); + ASSERT_TRUE(dicom.GetTagValue(tag, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ(tag, testEncodingsExpected[i]); + + { + Json::Value v; + dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); + ASSERT_STREQ(v["SpecificCharacterSet"].asCString(), "ISO_IR 192"); + ASSERT_STREQ(v["PatientName"].asCString(), testEncodingsExpected[i]); + } + + dicom.ChangeEncoding(testEncodings[i]); + + ASSERT_EQ(testEncodings[i], dicom.GetEncoding()); + + const char* c = NULL; + ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_PatientName, c).good()); + EXPECT_STREQ(c, testEncodingsEncoded[i]); + + ASSERT_TRUE(dicom.GetTagValue(tag, DICOM_TAG_PATIENT_NAME)); // Decodes to UTF-8 + EXPECT_EQ(tag, testEncodingsExpected[i]); + + { + Json::Value v; + dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); + ASSERT_STREQ(v["SpecificCharacterSet"].asCString(), GetDicomSpecificCharacterSet(testEncodings[i])); + ASSERT_STREQ(v["PatientName"].asCString(), testEncodingsExpected[i]); + } + } + } +} + + +TEST(Toolbox, CaseWithAccents) +{ + ASSERT_EQ(toUpperResult, Toolbox::ToUpperCaseWithAccents(toUpperSource)); +} + + + +TEST(ParsedDicomFile, InvalidCharacterSets) +{ + { + // No encoding provided, fallback to default encoding + DicomMap m; + m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); + + ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */); + ASSERT_EQ(Encoding_Latin3, d.GetEncoding()); + } + + { + // Valid encoding, "ISO_IR 13" is Japanese + DicomMap m; + m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 13", false); + m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); + + ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */); + ASSERT_EQ(Encoding_Japanese, d.GetEncoding()); + } + + { + // Invalid value for an encoding ("nope" is not in the DICOM standard) + DicomMap m; + m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "nope", false); + m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); + + ASSERT_THROW(ParsedDicomFile d(m, Encoding_Latin3), OrthancException); + } + + { + // Invalid encoding, as provided as a binary string + DicomMap m; + m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 13", true); + m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); + + ASSERT_THROW(ParsedDicomFile d(m, Encoding_Latin3), OrthancException); + } + + { + // Encoding provided as an empty string, fallback to default encoding + // In Orthanc <= 1.3.1, this test was throwing an exception + DicomMap m; + m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "", false); + m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); + + ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */); + ASSERT_EQ(Encoding_Latin3, d.GetEncoding()); + } +}
--- a/UnitTestsSources/ImageProcessingTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/ImageProcessingTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,11 @@ #include "gtest/gtest.h" #include "../Core/DicomFormat/DicomImageInformation.h" -#include "../Core/Images/ImageBuffer.h" +#include "../Core/Images/Image.h" #include "../Core/Images/ImageProcessing.h" +#include "../Core/Images/ImageTraits.h" + +#include <memory> using namespace Orthanc; @@ -44,18 +48,18 @@ { // Cardiac/MR* DicomMap m; - m.SetValue(DICOM_TAG_ROWS, "24"); - m.SetValue(DICOM_TAG_COLUMNS, "16"); - m.SetValue(DICOM_TAG_BITS_ALLOCATED, "16"); - m.SetValue(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); - m.SetValue(DICOM_TAG_BITS_STORED, "12"); - m.SetValue(DICOM_TAG_HIGH_BIT, "11"); - m.SetValue(DICOM_TAG_PIXEL_REPRESENTATION, "0"); - m.SetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); + m.SetValue(DICOM_TAG_ROWS, "24", false); + m.SetValue(DICOM_TAG_COLUMNS, "16", false); + m.SetValue(DICOM_TAG_BITS_ALLOCATED, "16", false); + m.SetValue(DICOM_TAG_SAMPLES_PER_PIXEL, "1", false); + m.SetValue(DICOM_TAG_BITS_STORED, "12", false); + m.SetValue(DICOM_TAG_HIGH_BIT, "11", false); + m.SetValue(DICOM_TAG_PIXEL_REPRESENTATION, "0", false); + m.SetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2", false); DicomImageInformation info(m); PixelFormat format; - ASSERT_TRUE(info.ExtractPixelFormat(format)); + ASSERT_TRUE(info.ExtractPixelFormat(format, false)); ASSERT_EQ(PixelFormat_Grayscale16, format); } @@ -64,17 +68,133 @@ { // Delphine CT DicomMap m; - m.SetValue(DICOM_TAG_ROWS, "24"); - m.SetValue(DICOM_TAG_COLUMNS, "16"); - m.SetValue(DICOM_TAG_BITS_ALLOCATED, "16"); - m.SetValue(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); - m.SetValue(DICOM_TAG_BITS_STORED, "16"); - m.SetValue(DICOM_TAG_HIGH_BIT, "15"); - m.SetValue(DICOM_TAG_PIXEL_REPRESENTATION, "1"); - m.SetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); + m.SetValue(DICOM_TAG_ROWS, "24", false); + m.SetValue(DICOM_TAG_COLUMNS, "16", false); + m.SetValue(DICOM_TAG_BITS_ALLOCATED, "16", false); + m.SetValue(DICOM_TAG_SAMPLES_PER_PIXEL, "1", false); + m.SetValue(DICOM_TAG_BITS_STORED, "16", false); + m.SetValue(DICOM_TAG_HIGH_BIT, "15", false); + m.SetValue(DICOM_TAG_PIXEL_REPRESENTATION, "1", false); + m.SetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2", false); DicomImageInformation info(m); PixelFormat format; - ASSERT_TRUE(info.ExtractPixelFormat(format)); + ASSERT_TRUE(info.ExtractPixelFormat(format, false)); ASSERT_EQ(PixelFormat_SignedGrayscale16, format); } + + + +namespace +{ + template <typename T> + class TestImageTraits : public ::testing::Test + { + private: + std::auto_ptr<Image> image_; + + protected: + virtual void SetUp() + { + image_.reset(new Image(ImageTraits::PixelTraits::GetPixelFormat(), 7, 9, false)); + } + + virtual void TearDown() + { + image_.reset(NULL); + } + + public: + typedef T ImageTraits; + + ImageAccessor& GetImage() + { + return *image_; + } + }; + + template <typename T> + class TestIntegerImageTraits : public TestImageTraits<T> + { + }; +} + + +typedef ::testing::Types< + ImageTraits<PixelFormat_Grayscale8>, + ImageTraits<PixelFormat_Grayscale16>, + ImageTraits<PixelFormat_SignedGrayscale16> + > IntegerFormats; +TYPED_TEST_CASE(TestIntegerImageTraits, IntegerFormats); + +typedef ::testing::Types< + ImageTraits<PixelFormat_Grayscale8>, + ImageTraits<PixelFormat_Grayscale16>, + ImageTraits<PixelFormat_SignedGrayscale16>, + ImageTraits<PixelFormat_RGB24>, + ImageTraits<PixelFormat_BGRA32> + > AllFormats; +TYPED_TEST_CASE(TestImageTraits, AllFormats); + + +TYPED_TEST(TestImageTraits, SetZero) +{ + ImageAccessor& image = this->GetImage(); + + memset(image.GetBuffer(), 128, image.GetHeight() * image.GetWidth()); + + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + ImageProcessing::Set(image, 0); + break; + + case PixelFormat_RGB24: + case PixelFormat_BGRA32: + ImageProcessing::Set(image, 0, 0, 0, 0); + break; + + default: + ASSERT_TRUE(0); + } + + typename TestFixture::ImageTraits::PixelType zero, value; + TestFixture::ImageTraits::PixelTraits::SetZero(zero); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + for (unsigned int x = 0; x < image.GetWidth(); x++) + { + TestFixture::ImageTraits::GetPixel(value, image, x, y); + ASSERT_TRUE(TestFixture::ImageTraits::PixelTraits::IsEqual(zero, value)); + } + } +} + + +TYPED_TEST(TestIntegerImageTraits, SetZeroFloat) +{ + ImageAccessor& image = this->GetImage(); + + memset(image.GetBuffer(), 128, image.GetHeight() * image.GetWidth()); + + unsigned int c = 0; + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + for (unsigned int x = 0; x < image.GetWidth(); x++, c++) + { + TestFixture::ImageTraits::SetFloatPixel(image, c, x, y); + } + } + + c = 0; + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + for (unsigned int x = 0; x < image.GetWidth(); x++, c++) + { + ASSERT_FLOAT_EQ(c, TestFixture::ImageTraits::GetFloatPixel(image, x, y)); + } + } +}
--- a/UnitTestsSources/ImageTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/ImageTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +41,11 @@ #include "../Core/Images/JpegWriter.h" #include "../Core/Images/PngReader.h" #include "../Core/Images/PngWriter.h" +#include "../Core/Images/PamReader.h" +#include "../Core/Images/PamWriter.h" #include "../Core/Toolbox.h" -#include "../Core/Uuid.h" -#include "../OrthancServer/OrthancInitialization.h" +#include "../Core/TemporaryFile.h" +#include "../OrthancServer/OrthancInitialization.h" // For the FontRegistry #include <stdint.h> @@ -66,10 +69,13 @@ } } - w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_RGB24, width, height, pitch, &image[0]); + + w.WriteToFile("UnitTestsResults/ColorPattern.png", accessor); std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); Orthanc::Toolbox::ComputeMD5(md5, f); ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); } @@ -91,10 +97,13 @@ } } - w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale8, width, height, pitch, &image[0]); + + w.WriteToFile("UnitTestsResults/Gray8Pattern.png", accessor); std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); Orthanc::Toolbox::ComputeMD5(md5, f); ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); } @@ -118,10 +127,12 @@ } } - w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale16, width, height, pitch, &image[0]); + w.WriteToFile("UnitTestsResults/Gray16Pattern.png", accessor); std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); Orthanc::Toolbox::ComputeMD5(md5, f); ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); } @@ -145,8 +156,11 @@ } } + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale16, width, height, pitch, &image[0]); + std::string s; - w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + w.WriteToMemory(s, accessor); { Orthanc::PngReader r; @@ -169,8 +183,8 @@ } { - Orthanc::Toolbox::TemporaryFile tmp; - Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); + Orthanc::TemporaryFile tmp; + Orthanc::SystemToolbox::WriteFile(s, tmp.GetPath()); Orthanc::PngReader r2; r2.ReadFromFile(tmp.GetPath()); @@ -200,7 +214,7 @@ std::string s; { - Orthanc::Image img(Orthanc::PixelFormat_Grayscale8, 16, 16); + Orthanc::Image img(Orthanc::PixelFormat_Grayscale8, 16, 16, false); for (unsigned int y = 0, value = 0; y < img.GetHeight(); y++) { uint8_t* p = reinterpret_cast<uint8_t*>(img.GetRow(y)); @@ -214,10 +228,10 @@ w.WriteToFile("UnitTestsResults/hello.jpg", img); w.WriteToMemory(s, img); - Orthanc::Toolbox::WriteFile(s, "UnitTestsResults/hello2.jpg"); + Orthanc::SystemToolbox::WriteFile(s, "UnitTestsResults/hello2.jpg"); std::string t; - Orthanc::Toolbox::ReadFile(t, "UnitTestsResults/hello.jpg"); + Orthanc::SystemToolbox::ReadFile(t, "UnitTestsResults/hello.jpg"); ASSERT_EQ(s.size(), t.size()); ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size())); } @@ -225,12 +239,12 @@ { Orthanc::JpegReader r1, r2; r1.ReadFromFile("UnitTestsResults/hello.jpg"); - ASSERT_EQ(16, r1.GetWidth()); - ASSERT_EQ(16, r1.GetHeight()); + ASSERT_EQ(16u, r1.GetWidth()); + ASSERT_EQ(16u, r1.GetHeight()); r2.ReadFromMemory(s); - ASSERT_EQ(16, r2.GetWidth()); - ASSERT_EQ(16, r2.GetHeight()); + ASSERT_EQ(16u, r2.GetWidth()); + ASSERT_EQ(16u, r2.GetHeight()); for (unsigned int y = 0; y < r1.GetHeight(); y++) { @@ -247,13 +261,171 @@ TEST(Font, Basic) { - Orthanc::Image s(Orthanc::PixelFormat_RGB24, 640, 480); + Orthanc::Image s(Orthanc::PixelFormat_RGB24, 640, 480, false); memset(s.GetBuffer(), 0, s.GetPitch() * s.GetHeight()); - ASSERT_GE(1, Orthanc::Configuration::GetFontRegistry().GetSize()); + ASSERT_GE(1u, Orthanc::Configuration::GetFontRegistry().GetSize()); Orthanc::Configuration::GetFontRegistry().GetFont(0).Draw(s, "Hello world É\n\rComment ça va ?\nq", 50, 60, 255, 0, 0); Orthanc::PngWriter w; w.WriteToFile("UnitTestsResults/font.png", s); } +TEST(PamWriter, ColorPattern) +{ + Orthanc::PamWriter w; + unsigned int width = 17; + unsigned int height = 61; + unsigned int pitch = width * 3; + + std::vector<uint8_t> image(height * pitch); + for (unsigned int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (unsigned int x = 0; x < width; x++, p += 3) + { + p[0] = (y % 3 == 0) ? 255 : 0; + p[1] = (y % 3 == 1) ? 255 : 0; + p[2] = (y % 3 == 2) ? 255 : 0; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_RGB24, width, height, pitch, &image[0]); + + w.WriteToFile("UnitTestsResults/ColorPattern.pam", accessor); + + std::string f, md5; + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/ColorPattern.pam"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("81a3441754e88969ebbe53e69891e841", md5); +} + +TEST(PamWriter, Gray8Pattern) +{ + Orthanc::PamWriter w; + int width = 17; + int height = 256; + int pitch = width; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p++) + { + *p = y; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale8, width, height, pitch, &image[0]); + + w.WriteToFile("UnitTestsResults/Gray8Pattern.pam", accessor); + + std::string f, md5; + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.pam"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("7873c408d26a9d11dd1c1de5e69cc0a3", md5); +} + +TEST(PamWriter, Gray16Pattern) +{ + Orthanc::PamWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale16, width, height, pitch, &image[0]); + w.WriteToFile("UnitTestsResults/Gray16Pattern.pam", accessor); + + std::string f, md5; + Orthanc::SystemToolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.pam"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("b268772bf28f3b2b8520ff21c5e3dcb6", md5); +} + +TEST(PamWriter, EndToEnd) +{ + Orthanc::PamWriter w; + unsigned int width = 256; + unsigned int height = 256; + unsigned int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (unsigned int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(Orthanc::PixelFormat_Grayscale16, width, height, pitch, &image[0]); + + std::string s; + w.WriteToMemory(s, accessor); + + { + Orthanc::PamReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); + + v = 0; + for (unsigned int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast<const uint16_t*> + ((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); + ASSERT_EQ(p, r.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(v, *p); + } + } + } + + { + Orthanc::TemporaryFile tmp; + Orthanc::SystemToolbox::WriteFile(s, tmp.GetPath()); + + Orthanc::PamReader r2; + r2.ReadFromFile(tmp.GetPath()); + + ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r2.GetWidth(), width); + ASSERT_EQ(r2.GetHeight(), height); + + v = 0; + for (unsigned int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast<const uint16_t*> + ((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); + ASSERT_EQ(p, r2.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } +} +
--- a/UnitTestsSources/JpegLosslessTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/JpegLosslessTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,13 +34,13 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../OrthancServer/Internals/DicomImageDecoder.h" +#include "../Core/DicomParsing/Internals/DicomImageDecoder.h" -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 +#if ORTHANC_ENABLE_JPEG_LOSSLESS == 1 #include <dcmtk/dcmdata/dcfilefo.h> -#include "../OrthancServer/ParsedDicomFile.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "../Core/OrthancException.h" #include "../Core/Images/ImageBuffer.h" #include "../Core/Images/PngWriter.h"
--- a/UnitTestsSources/LuaTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/LuaTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +260,9 @@ ASSERT_EQ(Json::stringValue, v["List"][0]["a"].type()); ASSERT_EQ(Json::stringValue, v["List"][0]["b"].type()); ASSERT_EQ(Json::stringValue, v["List"][0]["c"].type()); - ASSERT_EQ("42", v["List"][0]["a"].asString()); - ASSERT_EQ("44.37", v["List"][0]["b"].asString()); - ASSERT_EQ("-43", v["List"][0]["c"].asString()); + ASSERT_FLOAT_EQ(42.0f, boost::lexical_cast<float>(v["List"][0]["a"].asString())); + ASSERT_FLOAT_EQ(44.37f, boost::lexical_cast<float>(v["List"][0]["b"].asString())); + ASSERT_FLOAT_EQ(-43.0f, boost::lexical_cast<float>(v["List"][0]["c"].asString())); ASSERT_EQ("test3", v["List"][1][0].asString()); ASSERT_EQ("test1", v["List"][1][1].asString()); ASSERT_EQ("test2", v["List"][1][2].asString()); @@ -286,9 +287,14 @@ { Orthanc::LuaContext lua; -#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 - lua.Execute("JSON = loadstring(HttpGet('http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/JSON.lua')) ()"); - const std::string url("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/Product.json"); +#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 + // The "http://www.orthanc-server.com/downloads/third-party/" does + // not automatically redirect to HTTPS, so we cas use it even if the + // OpenSSL/HTTPS support is disabled in curl + const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/"; + + lua.Execute("JSON = loadstring(HttpGet('" + BASE + "JSON.lua')) ()"); + const std::string url(BASE + "Product.json"); #endif std::string s;
--- a/UnitTestsSources/MemoryCacheTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/MemoryCacheTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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/MultiThreadingTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/MultiThreadingTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,17 +34,205 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../OrthancServer/Scheduler/ServerScheduler.h" +#include "../Core/FileStorage/MemoryStorageArea.h" +#include "../Core/JobsEngine/JobsEngine.h" +#include "../Core/Logging.h" +#include "../Core/MultiThreading/SharedMessageQueue.h" #include "../Core/OrthancException.h" +#include "../Core/SerializationToolbox.h" +#include "../Core/SystemToolbox.h" #include "../Core/Toolbox.h" -#include "../Core/MultiThreading/Locker.h" -#include "../Core/MultiThreading/Mutex.h" -#include "../Core/MultiThreading/ReaderWriterLock.h" +#include "../OrthancServer/DatabaseWrapper.h" +#include "../OrthancServer/ServerContext.h" +#include "../OrthancServer/ServerJobs/LuaJobManager.h" +#include "../OrthancServer/ServerJobs/OrthancJobUnserializer.h" + +#include "../Core/JobsEngine/Operations/JobOperationValues.h" +#include "../Core/JobsEngine/Operations/NullOperationValue.h" +#include "../Core/JobsEngine/Operations/StringOperationValue.h" +#include "../OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h" + +#include "../Core/JobsEngine/Operations/LogJobOperation.h" +#include "../OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h" +#include "../OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h" +#include "../OrthancServer/ServerJobs/Operations/StorePeerOperation.h" +#include "../OrthancServer/ServerJobs/Operations/StoreScuOperation.h" +#include "../OrthancServer/ServerJobs/Operations/SystemCallOperation.h" + +#include "../OrthancServer/ServerJobs/ArchiveJob.h" +#include "../OrthancServer/ServerJobs/DicomModalityStoreJob.h" +#include "../OrthancServer/ServerJobs/MergeStudyJob.h" +#include "../OrthancServer/ServerJobs/OrthancPeerStoreJob.h" +#include "../OrthancServer/ServerJobs/ResourceModificationJob.h" +#include "../OrthancServer/ServerJobs/SplitStudyJob.h" + using namespace Orthanc; namespace { + class DummyJob : public IJob + { + private: + bool fails_; + unsigned int count_; + unsigned int steps_; + + public: + DummyJob() : + fails_(false), + count_(0), + steps_(4) + { + } + + explicit DummyJob(bool fails) : + fails_(fails), + count_(0), + steps_(4) + { + } + + virtual void Start() + { + } + + virtual void Reset() + { + } + + virtual JobStepResult Step() + { + if (fails_) + { + return JobStepResult::Failure(ErrorCode_ParameterOutOfRange); + } + else if (count_ == steps_ - 1) + { + return JobStepResult::Success(); + } + else + { + count_++; + return JobStepResult::Continue(); + } + } + + virtual void Stop(JobStopReason reason) + { + } + + virtual float GetProgress() + { + return static_cast<float>(count_) / static_cast<float>(steps_ - 1); + } + + virtual void GetJobType(std::string& type) + { + type = "DummyJob"; + } + + virtual bool Serialize(Json::Value& value) + { + value = Json::objectValue; + value["Type"] = "DummyJob"; + return true; + } + + virtual void GetPublicContent(Json::Value& value) + { + value["hello"] = "world"; + } + }; + + + class DummyInstancesJob : public SetOfInstancesJob + { + private: + bool trailingStepDone_; + + protected: + virtual bool HandleInstance(const std::string& instance) + { + return (instance != "nope"); + } + + virtual bool HandleTrailingStep() + { + if (HasTrailingStep()) + { + if (trailingStepDone_) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + trailingStepDone_ = true; + return true; + } + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + public: + DummyInstancesJob() : + trailingStepDone_(false) + { + } + + DummyInstancesJob(const Json::Value& value) : + SetOfInstancesJob(value) + { + if (HasTrailingStep()) + { + trailingStepDone_ = (GetPosition() == GetCommandsCount()); + } + else + { + trailingStepDone_ = false; + } + } + + bool IsTrailingStepDone() const + { + return trailingStepDone_; + } + + virtual void Stop(JobStopReason reason) + { + } + + virtual void GetJobType(std::string& s) + { + s = "DummyInstancesJob"; + } + }; + + + class DummyUnserializer : public GenericJobUnserializer + { + public: + virtual IJob* UnserializeJob(const Json::Value& value) + { + if (SerializationToolbox::ReadString(value, "Type") == "DummyInstancesJob") + { + return new DummyInstancesJob(value); + } + else if (SerializationToolbox::ReadString(value, "Type") == "DummyJob") + { + return new DummyJob; + } + else + { + return GenericJobUnserializer::UnserializeJob(value); + } + } + }; + + class DynamicInteger : public IDynamicObject { private: @@ -104,156 +293,1679 @@ } -TEST(MultiThreading, Mutex) + + +static bool CheckState(JobsRegistry& registry, + const std::string& id, + JobState state) { - Mutex mutex; - Locker locker(mutex); + JobState s; + if (registry.GetState(s, id)) + { + return state == s; + } + else + { + return false; + } } -TEST(MultiThreading, ReaderWriterLock) +static bool CheckErrorCode(JobsRegistry& registry, + const std::string& id, + ErrorCode code) { - ReaderWriterLock lock; - + JobInfo s; + if (registry.GetJobInfo(s, id)) { - Locker locker1(lock.ForReader()); - Locker locker2(lock.ForReader()); + return code == s.GetStatus().GetErrorCode(); } - + else { - Locker locker3(lock.ForWriter()); + return false; } } +TEST(JobsRegistry, Priority) +{ + JobsRegistry registry; -#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" + std::string i1, i2, i3, i4; + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new DummyJob(), 30); + registry.Submit(i3, new DummyJob(), 20); + registry.Submit(i4, new DummyJob(), 5); + + registry.SetMaxCompletedJobs(2); + + std::set<std::string> id; + registry.ListJobs(id); + + ASSERT_EQ(4u, id.size()); + ASSERT_TRUE(id.find(i1) != id.end()); + ASSERT_TRUE(id.find(i2) != id.end()); + ASSERT_TRUE(id.find(i3) != id.end()); + ASSERT_TRUE(id.find(i4) != id.end()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(30, job.GetPriority()); + ASSERT_EQ(i2, job.GetId()); + + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i2, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i3, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(20, job.GetPriority()); + ASSERT_EQ(i3, job.GetId()); + + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i3, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i3, JobState_Success)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(10, job.GetPriority()); + ASSERT_EQ(i1, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(5, job.GetPriority()); + ASSERT_EQ(i4, job.GetId()); + } + + { + JobsRegistry::RunningJob job(registry, 1); + ASSERT_FALSE(job.IsValid()); + } + + JobState s; + ASSERT_TRUE(registry.GetState(s, i1)); + ASSERT_FALSE(registry.GetState(s, i2)); // Removed because oldest + ASSERT_FALSE(registry.GetState(s, i3)); // Removed because second oldest + ASSERT_TRUE(registry.GetState(s, i4)); + + registry.SetMaxCompletedJobs(1); // (*) + ASSERT_FALSE(registry.GetState(s, i1)); // Just discarded by (*) + ASSERT_TRUE(registry.GetState(s, i4)); +} + + +TEST(JobsRegistry, Simultaneous) +{ + JobsRegistry registry; + + std::string i1, i2; + registry.Submit(i1, new DummyJob(), 20); + registry.Submit(i2, new DummyJob(), 10); -TEST(ReusableDicomUserConnection, DISABLED_Basic) + ASSERT_TRUE(CheckState(registry, i1, JobState_Pending)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Pending)); + + { + JobsRegistry::RunningJob job1(registry, 0); + JobsRegistry::RunningJob job2(registry, 0); + + ASSERT_TRUE(job1.IsValid()); + ASSERT_TRUE(job2.IsValid()); + + job1.MarkFailure(); + job2.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, i1, JobState_Running)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, i1, JobState_Failure)); + ASSERT_TRUE(CheckState(registry, i2, JobState_Success)); +} + + +TEST(JobsRegistry, Resubmit) { - ReusableDicomUserConnection c; - c.SetMillisecondsBeforeClose(200); - printf("START\n"); fflush(stdout); + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkFailure(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Retry) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkRetry(0); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.ScheduleRetries(); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); { - RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + job.MarkSuccess(); + + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PausePending) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); +} + + +TEST(JobsRegistry, PauseRunning) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + registry.Resubmit(id); + job.MarkPause(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resubmit(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, PauseRetry) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkRetry(0); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); } - printf("**\n"); fflush(stdout); - Toolbox::USleep(1000000); - printf("**\n"); fflush(stdout); + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + + registry.Pause(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + + registry.Resume(id); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); { - RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); +} + + +TEST(JobsRegistry, Cancel) +{ + JobsRegistry registry; + + std::string id; + registry.Submit(id, new DummyJob(), 10); + + ASSERT_FALSE(registry.Cancel("nope")); + + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + job.MarkSuccess(); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); } - Toolbox::ServerBarrier(); - printf("DONE\n"); fflush(stdout); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Success)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + registry.Submit(id, new DummyJob(), 10); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkCanceled(); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Pause(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Paused)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + ASSERT_TRUE(registry.Resubmit(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Pending)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); + + { + JobsRegistry::RunningJob job(registry, 0); + ASSERT_TRUE(job.IsValid()); + ASSERT_EQ(id, job.GetId()); + + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + ASSERT_TRUE(CheckState(registry, id, JobState_Running)); + + job.MarkRetry(500); + } + + ASSERT_TRUE(CheckState(registry, id, JobState_Retry)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_Success)); + + ASSERT_TRUE(registry.Cancel(id)); + ASSERT_TRUE(CheckState(registry, id, JobState_Failure)); + ASSERT_TRUE(CheckErrorCode(registry, id, ErrorCode_CanceledJob)); } -class Tutu : public IServerCommand +TEST(JobsEngine, SubmitAndWait) +{ + JobsEngine engine; + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + Json::Value content = Json::nullValue; + ASSERT_TRUE(engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10)); + ASSERT_EQ(Json::objectValue, content.type()); + ASSERT_EQ("world", content["hello"].asString()); + + content = Json::nullValue; + ASSERT_FALSE(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10)); + ASSERT_EQ(Json::nullValue, content.type()); + + engine.Stop(); +} + + +TEST(JobsEngine, DISABLED_SequenceOfOperationsJob) +{ + JobsEngine engine; + engine.SetThreadSleep(10); + engine.SetWorkersCount(3); + engine.Start(); + + std::string id; + SequenceOfOperationsJob* job = NULL; + + { + std::auto_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob); + job = a.get(); + engine.GetRegistry().Submit(id, a.release(), 0); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + + { + SequenceOfOperationsJob::Lock lock(*job); + size_t i = lock.AddOperation(new LogJobOperation); + size_t j = lock.AddOperation(new LogJobOperation); + size_t k = lock.AddOperation(new LogJobOperation); + + StringOperationValue a("Hello"); + StringOperationValue b("World"); + lock.AddInput(i, a); + lock.AddInput(i, b); + + lock.Connect(i, j); + lock.Connect(j, k); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + + engine.Stop(); + +} + + +TEST(JobsEngine, DISABLED_Lua) +{ + JobsEngine engine; + engine.SetThreadSleep(10); + engine.SetWorkersCount(2); + engine.Start(); + + LuaJobManager lua; + lua.SetMaxOperationsPerJob(5); + lua.SetTrailingOperationTimeout(200); + + for (size_t i = 0; i < 30; i++) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(150)); + + LuaJobManager::Lock lock(lua, engine); + size_t a = lock.AddLogOperation(); + size_t b = lock.AddLogOperation(); + size_t c = lock.AddSystemCallOperation("echo"); + lock.AddStringInput(a, boost::lexical_cast<std::string>(i)); + lock.AddNullInput(a); + lock.Connect(a, b); + lock.Connect(a, c); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + + engine.Stop(); +} + + +static bool CheckSameJson(const Json::Value& a, + const Json::Value& b) +{ + std::string s = a.toStyledString(); + std::string t = b.toStyledString(); + + if (s == t) + { + return true; + } + else + { + LOG(ERROR) << "Expected serialization: " << s; + LOG(ERROR) << "Actual serialization: " << t; + return false; + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJob& job) { -private: - int factor_; + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::auto_ptr<IJob> unserialized(unserializer.UnserializeJob(a)); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return (CheckSameJson(a, b)); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSetOfInstances(IJobUnserializer& unserializer, + SetOfInstancesJob& job) +{ + Json::Value a = 42; + + if (!job.Serialize(a)) + { + return false; + } + else + { + std::auto_ptr<SetOfInstancesJob> unserialized + (dynamic_cast<SetOfInstancesJob*>(unserializer.UnserializeJob(a))); + + Json::Value b = 43; + if (unserialized->Serialize(b)) + { + return (CheckSameJson(a, b) && + job.HasTrailingStep() == unserialized->HasTrailingStep() && + job.GetPosition() == unserialized->GetPosition() && + job.GetInstancesCount() == unserialized->GetInstancesCount() && + job.GetCommandsCount() == unserialized->GetCommandsCount()); + } + else + { + return false; + } + } +} + + +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + IJobOperation& operation) +{ + Json::Value a = 42; + operation.Serialize(a); + + std::auto_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + -public: - Tutu(int f) : factor_(f) +static bool CheckIdempotentSerialization(IJobUnserializer& unserializer, + JobOperationValue& value) +{ + Json::Value a = 42; + value.Serialize(a); + + std::auto_ptr<JobOperationValue> unserialized(unserializer.UnserializeValue(a)); + + Json::Value b = 43; + unserialized->Serialize(b); + + return CheckSameJson(a, b); +} + + +TEST(JobsSerialization, BadFileFormat) +{ + GenericJobUnserializer unserializer; + + Json::Value s; + + s = Json::objectValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = Json::arrayValue; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = "hello"; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + s = 42; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); +} + + +TEST(JobsSerialization, JobOperationValues) +{ + Json::Value s; + { + JobOperationValues values; + values.Append(new NullOperationValue); + values.Append(new StringOperationValue("hello")); + values.Append(new StringOperationValue("world")); + + s = 42; + values.Serialize(s); + } + + { + GenericJobUnserializer unserializer; + std::auto_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()); + ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(2).GetType()); + + ASSERT_EQ("hello", dynamic_cast<const StringOperationValue&>(values->GetValue(1)).GetContent()); + ASSERT_EQ("world", dynamic_cast<const StringOperationValue&>(values->GetValue(2)).GetContent()); + } +} + + +TEST(JobsSerialization, GenericValues) +{ + GenericJobUnserializer unserializer; + Json::Value s; + + { + NullOperationValue null; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, null)); + null.Serialize(s); } - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::auto_ptr<JobOperationValue> value; + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_Null, value->GetType()); + + { + StringOperationValue str("Hello"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, str)); + str.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + value.reset(unserializer.UnserializeValue(s)); + + ASSERT_EQ(JobOperationValue::Type_String, value->GetType()); + ASSERT_EQ("Hello", dynamic_cast<StringOperationValue&>(*value).GetContent()); +} + + +TEST(JobsSerialization, GenericOperations) +{ + DummyUnserializer unserializer; + Json::Value s; + { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); ++it) + LogJobOperation operation; + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + + { + std::auto_ptr<IJobOperation> operation; + operation.reset(unserializer.UnserializeOperation(s)); + + // Make sure that we have indeed unserialized a log operation + Json::Value dummy; + ASSERT_THROW(dynamic_cast<DeleteResourceOperation&>(*operation).Serialize(dummy), std::bad_cast); + dynamic_cast<LogJobOperation&>(*operation).Serialize(dummy); + } +} + + +TEST(JobsSerialization, GenericJobs) +{ + Json::Value s; + + // This tests SetOfInstancesJob + + { + DummyInstancesJob job; + job.SetDescription("description"); + job.AddInstance("hello"); + job.AddInstance("nope"); + job.AddInstance("world"); + job.SetPermissive(true); + ASSERT_THROW(job.Step(), 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()); + { - int a = boost::lexical_cast<int>(*it); - int b = factor_ * a; + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + DummyUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); - printf("%d * %d = %d\n", a, factor_, b); + const DummyInstancesJob& tmp = dynamic_cast<const DummyInstancesJob&>(*job); + ASSERT_FALSE(tmp.IsStarted()); + ASSERT_TRUE(tmp.IsPermissive()); + ASSERT_EQ("description", tmp.GetDescription()); + ASSERT_EQ(3u, tmp.GetInstancesCount()); + ASSERT_EQ(2u, tmp.GetPosition()); + ASSERT_EQ(1u, tmp.GetFailedInstances().size()); + ASSERT_EQ("hello", tmp.GetInstance(0)); + ASSERT_EQ("nope", tmp.GetInstance(1)); + ASSERT_EQ("world", tmp.GetInstance(2)); + ASSERT_TRUE(tmp.IsFailedInstance("nope")); + } + + // SequenceOfOperationsJob - //if (a == 84) { printf("BREAK\n"); return false; } + { + SequenceOfOperationsJob job; + job.SetDescription("hello"); - outputs.push_back(boost::lexical_cast<std::string>(b)); + { + SequenceOfOperationsJob::Lock lock(job); + size_t a = lock.AddOperation(new LogJobOperation); + size_t b = lock.AddOperation(new LogJobOperation); + lock.Connect(a, b); + + StringOperationValue s1("hello"); + StringOperationValue s2("world"); + lock.AddInput(a, s1); + lock.AddInput(a, s2); + lock.SetDicomAssociationTimeout(200); + lock.SetTrailingOperationTimeout(300); } - Toolbox::USleep(30000); + ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + + { + GenericJobUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, job)); + } + + ASSERT_TRUE(job.Serialize(s)); + } + + { + GenericJobUnserializer unserializer; + ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException); + ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException); + + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + std::string tmp; + dynamic_cast<SequenceOfOperationsJob&>(*job).GetDescription(tmp); + ASSERT_EQ("hello", tmp); + } +} + + +static bool IsSameTagValue(ParsedDicomFile& dicom1, + ParsedDicomFile& dicom2, + DicomTag tag) +{ + std::string a, b; + return (dicom1.GetTagValue(a, tag) && + dicom2.GetTagValue(b, tag) && + (a == b)); +} + + + +TEST(JobsSerialization, DicomModification) +{ + Json::Value s; - return true; + 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); + + std::auto_ptr<ParsedDicomFile> modified(source.Clone(true)); + + { + DicomModification modification; + modification.SetLevel(ResourceType_Series); + modification.Clear(DICOM_TAG_STUDY_DESCRIPTION); + modification.Remove(DICOM_TAG_SERIES_DESCRIPTION); + modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true); + + modification.Apply(*modified); + + s = 42; + modification.Serialize(s); } -}; + + { + DicomModification modification(s); + ASSERT_EQ(ResourceType_Series, modification.GetLevel()); + + std::auto_ptr<ParsedDicomFile> second(source.Clone(true)); + modification.Apply(*second); + + std::string s; + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(s.empty()); + ASSERT_FALSE(second->GetTagValue(s, DICOM_TAG_SERIES_DESCRIPTION)); + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("Test 4", s); + + ASSERT_TRUE(IsSameTagValue(source, *modified, DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(source, *second, DICOM_TAG_STUDY_INSTANCE_UID)); + + ASSERT_FALSE(IsSameTagValue(source, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(*modified, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + } +} -static void Tata(ServerScheduler* s, ServerJob* j, bool* done) -{ - typedef IServerCommand::ListOfStrings ListOfStrings; +TEST(JobsSerialization, DicomInstanceOrigin) +{ + Json::Value s; + std::string t; + + { + DicomInstanceOrigin origin; + + s = 42; + origin.Serialize(s); + } - while (!(*done)) + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Unknown, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromDicomProtocol("host", "aet", "called")); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_DicomProtocol, origin.GetRequestOrigin()); + ASSERT_EQ("aet", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_TRUE(origin.LookupRemoteAet(t)); ASSERT_EQ("aet", t); + ASSERT_TRUE(origin.LookupCalledAet(t)); ASSERT_EQ("called", t); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + { - ListOfStrings l; - s->GetListOfJobs(l); - for (ListOfStrings::iterator it = l.begin(); it != l.end(); ++it) - { - printf(">> %s: %0.1f\n", it->c_str(), 100.0f * s->GetProgress(*it)); - } - Toolbox::USleep(3000); + DicomInstanceOrigin origin(DicomInstanceOrigin::FromHttp("host", "username")); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_RestApi, origin.GetRequestOrigin()); + ASSERT_EQ("", std::string(origin.GetRemoteAetC())); + ASSERT_TRUE(origin.LookupRemoteIp(t)); ASSERT_EQ("host", t); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_TRUE(origin.LookupHttpUsername(t)); ASSERT_EQ("username", t); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromLua()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Lua, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); + } + + { + DicomInstanceOrigin origin(DicomInstanceOrigin::FromPlugins()); + + s = 42; + origin.Serialize(s); + } + + { + DicomInstanceOrigin origin(s); + ASSERT_EQ(RequestOrigin_Plugins, origin.GetRequestOrigin()); + ASSERT_FALSE(origin.LookupRemoteIp(t)); + ASSERT_FALSE(origin.LookupRemoteAet(t)); + ASSERT_FALSE(origin.LookupCalledAet(t)); + ASSERT_FALSE(origin.LookupHttpUsername(t)); } } -TEST(MultiThreading, ServerScheduler) +namespace +{ + class OrthancJobsSerialization : public testing::Test + { + private: + MemoryStorageArea storage_; + DatabaseWrapper db_; // The SQLite DB is in memory + std::auto_ptr<ServerContext> context_; + TimeoutDicomConnectionManager manager_; + + public: + OrthancJobsSerialization() + { + db_.Open(); + context_.reset(new ServerContext(db_, storage_, true /* running unit tests */)); + context_->SetupJobsEngine(true, false); + } + + virtual ~OrthancJobsSerialization() + { + context_->Stop(); + context_.reset(NULL); + db_.Close(); + } + + ServerContext& GetContext() + { + return *context_; + } + + bool CreateInstance(std::string& id) + { + // Create a sample DICOM file + ParsedDicomFile dicom(true); + dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"), + false, DicomReplaceMode_InsertIfAbsent); + + DicomInstanceToStore toStore; + toStore.SetParsedDicomFile(dicom); + + return (context_->Store(id, toStore) == StoreStatus_Success); + } + }; +} + + +TEST_F(OrthancJobsSerialization, Values) { - ServerScheduler scheduler(10); + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + { + DicomInstanceOperationValue instance(GetContext(), id); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, instance)); + instance.Serialize(s); + } + + std::auto_ptr<JobOperationValue> value; + value.reset(unserializer.UnserializeValue(s)); + ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType()); + ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId()); + + { + std::string content; + dynamic_cast<DicomInstanceOperationValue&>(*value).ReadDicom(content); + + ParsedDicomFile dicom(content); + ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("JODOGNE", content); + } +} + + +TEST_F(OrthancJobsSerialization, Operations) +{ + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + Json::Value s; + OrthancJobUnserializer unserializer(GetContext()); + + // DeleteResourceOperation + + { + DeleteResourceOperation operation(GetContext()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + std::auto_ptr<IJobOperation> operation; + + { + operation.reset(unserializer.UnserializeOperation(s)); - ServerJob job; - ServerCommandInstance& f2 = job.AddCommand(new Tutu(2)); - ServerCommandInstance& f3 = job.AddCommand(new Tutu(3)); - ServerCommandInstance& f4 = job.AddCommand(new Tutu(4)); - ServerCommandInstance& f5 = job.AddCommand(new Tutu(5)); - f2.AddInput(boost::lexical_cast<std::string>(42)); - //f3.AddInput(boost::lexical_cast<std::string>(42)); - //f4.AddInput(boost::lexical_cast<std::string>(42)); - f2.ConnectOutput(f3); - f3.ConnectOutput(f4); - f4.ConnectOutput(f5); + Json::Value dummy; + ASSERT_THROW(dynamic_cast<LogJobOperation&>(*operation).Serialize(dummy), std::bad_cast); + dynamic_cast<DeleteResourceOperation&>(*operation).Serialize(dummy); + } + + // StorePeerOperation + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetCredentials("username", "password"); + peer.SetPkcs11Enabled(true); + + StorePeerOperation operation(peer); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const StorePeerOperation& tmp = dynamic_cast<StorePeerOperation&>(*operation); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + } + + // StoreScuOperation + + { + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + StoreScuOperation operation("TEST", modality); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); - f3.SetConnectedToSink(true); - f5.SetConnectedToSink(true); + const StoreScuOperation& tmp = dynamic_cast<StoreScuOperation&>(*operation); + ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); + ASSERT_EQ("TEST", tmp.GetLocalAet()); + } + + // SystemCallOperation + + { + SystemCallOperation operation(std::string("echo")); + operation.AddPreArgument("a"); + operation.AddPreArgument("b"); + operation.AddPostArgument("c"); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); - job.SetDescription("tutu"); + const SystemCallOperation& tmp = dynamic_cast<SystemCallOperation&>(*operation); + ASSERT_EQ("echo", tmp.GetCommand()); + ASSERT_EQ(2u, tmp.GetPreArgumentsCount()); + ASSERT_EQ(1u, tmp.GetPostArgumentsCount()); + ASSERT_EQ("a", tmp.GetPreArgument(0)); + ASSERT_EQ("b", tmp.GetPreArgument(1)); + ASSERT_EQ("c", tmp.GetPostArgument(0)); + } + + // ModifyInstanceOperation - bool done = false; - boost::thread t(Tata, &scheduler, &job, &done); + { + std::auto_ptr<DicomModification> modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release()); + + ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation)); + operation.Serialize(s); + } + + { + operation.reset(unserializer.UnserializeOperation(s)); + + const ModifyInstanceOperation& tmp = dynamic_cast<ModifyInstanceOperation&>(*operation); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } +} - //scheduler.Submit(job); +TEST_F(OrthancJobsSerialization, Jobs) +{ + Json::Value s; + + // ArchiveJob + + { + boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); + ArchiveJob job(tmp, GetContext(), false, false); + ASSERT_FALSE(job.Serialize(s)); // Cannot serialize this + } + + // DicomModalityStoreJob + + OrthancJobUnserializer unserializer(GetContext()); + + { + RemoteModalityParameters modality; + modality.SetApplicationEntityTitle("REMOTE"); + modality.SetHost("192.168.1.1"); + modality.SetPortNumber(1000); + modality.SetManufacturer(ModalityManufacturer_StoreScp); + + DicomModalityStoreJob job(GetContext()); + job.SetLocalAet("LOCAL"); + job.SetRemoteModality(modality); + job.SetMoveOriginator("MOVESCU", 42); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + DicomModalityStoreJob& tmp = dynamic_cast<DicomModalityStoreJob&>(*job); + ASSERT_EQ("LOCAL", tmp.GetLocalAet()); + ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost()); + ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer()); + ASSERT_TRUE(tmp.HasMoveOriginator()); + ASSERT_EQ("MOVESCU", tmp.GetMoveOriginatorAet()); + ASSERT_EQ(42, tmp.GetMoveOriginatorId()); + } + + // OrthancPeerStoreJob + + { + WebServiceParameters peer; + peer.SetUrl("http://localhost/"); + peer.SetCredentials("username", "password"); + peer.SetPkcs11Enabled(true); - IServerCommand::ListOfStrings l; - scheduler.SubmitAndWait(l, job); + OrthancPeerStoreJob job(GetContext()); + job.SetPeer(peer); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job); + ASSERT_EQ("http://localhost/", tmp.GetPeer().GetUrl()); + ASSERT_EQ("username", tmp.GetPeer().GetUsername()); + ASSERT_EQ("password", tmp.GetPeer().GetPassword()); + ASSERT_TRUE(tmp.GetPeer().IsPkcs11Enabled()); + } + + // ResourceModificationJob + + { + std::auto_ptr<DicomModification> modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); + + ResourceModificationJob job(GetContext()); + job.SetModification(modification.release(), ResourceType_Patient, true); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job); + ASSERT_TRUE(tmp.IsAnonymization()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } + + // SplitStudyJob + + std::string instance; + ASSERT_TRUE(CreateInstance(instance)); + + std::string study, series; + + { + ServerContext::DicomCacheLocker lock(GetContext(), instance); + study = lock.GetDicom().GetHasher().HashStudy(); + series = lock.GetDicom().GetHasher().HashSeries(); + } + + { + std::list<std::string> tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(1u, tmp.size()); + ASSERT_EQ(study, tmp.front()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(1u, tmp.size()); + ASSERT_EQ(series, tmp.front()); + } - ASSERT_EQ(2u, l.size()); - ASSERT_EQ(42 * 2 * 3, boost::lexical_cast<int>(l.front())); - ASSERT_EQ(42 * 2 * 3 * 4 * 5, boost::lexical_cast<int>(l.back())); + std::string study2; + + { + std::string a, b; + + { + ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); + + SplitStudyJob job(GetContext(), study); + job.SetKeepSource(true); + job.AddSourceSeries(series); + ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + job.Replace(DICOM_TAG_PATIENT_NAME, "hello"); + job.Remove(DICOM_TAG_PATIENT_BIRTH_DATE); + ASSERT_THROW(job.Replace(DICOM_TAG_SERIES_DESCRIPTION, "nope"), OrthancException); + ASSERT_THROW(job.Remove(DICOM_TAG_SERIES_DESCRIPTION), OrthancException); + + ASSERT_TRUE(job.GetTargetStudy().empty()); + a = job.GetTargetStudyUid(); + ASSERT_TRUE(job.LookupTargetSeriesUid(b, series)); + + job.AddTrailingStep(); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + + study2 = job.GetTargetStudy(); + ASSERT_FALSE(study2.empty()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + SplitStudyJob& tmp = dynamic_cast<SplitStudyJob&>(*job); + ASSERT_TRUE(tmp.IsKeepSource()); + ASSERT_EQ(study, tmp.GetSourceStudy()); + ASSERT_EQ(a, tmp.GetTargetStudyUid()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + + std::string s; + ASSERT_EQ(study2, tmp.GetTargetStudy()); + ASSERT_FALSE(tmp.LookupTargetSeriesUid(s, "nope")); + ASSERT_TRUE(tmp.LookupTargetSeriesUid(s, series)); + ASSERT_EQ(b, s); + + ASSERT_FALSE(tmp.LookupReplacement(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(tmp.LookupReplacement(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("hello", s); + ASSERT_FALSE(tmp.IsRemoved(DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(tmp.IsRemoved(DICOM_TAG_PATIENT_BIRTH_DATE)); + } + } - for (IServerCommand::ListOfStrings::iterator i = l.begin(); i != l.end(); i++) + { + std::list<std::string> tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(2u, tmp.size()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(2u, tmp.size()); + } + + // MergeStudyJob + { - printf("** %s\n", i->c_str()); + ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); + + MergeStudyJob job(GetContext(), study); + job.SetKeepSource(true); + job.AddSource(study2); + ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); + ASSERT_THROW(job.AddSourceStudy("nope"), OrthancException); + ASSERT_THROW(job.AddSource("nope"), OrthancException); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + ASSERT_EQ(job.GetTargetStudy(), study); + + job.AddTrailingStep(); + job.Start(); + ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::list<std::string> tmp; + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Study); + ASSERT_EQ(2u, tmp.size()); + GetContext().GetIndex().GetAllUuids(tmp, ResourceType_Series); + ASSERT_EQ(3u, tmp.size()); + } + + { + std::auto_ptr<IJob> job; + job.reset(unserializer.UnserializeJob(s)); + + MergeStudyJob& tmp = dynamic_cast<MergeStudyJob&>(*job); + ASSERT_TRUE(tmp.IsKeepSource()); + ASSERT_EQ(study, tmp.GetTargetStudy()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + } +} + + +TEST(JobsSerialization, Registry) +{ + Json::Value s; + std::string i1, i2; + + { + JobsRegistry registry; + registry.Submit(i1, new DummyJob(), 10); + registry.Submit(i2, new SequenceOfOperationsJob(), 30); + registry.Serialize(s); } - //Toolbox::ServerBarrier(); - //Toolbox::USleep(3000000); - - scheduler.Stop(); + { + DummyUnserializer unserializer; + JobsRegistry registry(unserializer, s); - done = true; - if (t.joinable()) - { - t.join(); + Json::Value t; + registry.Serialize(t); + ASSERT_TRUE(CheckSameJson(s, t)); } } + + +TEST(JobsSerialization, TrailingStep) +{ + { + Json::Value s; + + DummyInstancesJob job; + ASSERT_EQ(0, job.GetCommandsCount()); + ASSERT_EQ(0, job.GetInstancesCount()); + + job.Start(); + ASSERT_EQ(0, job.GetPosition()); + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(1, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step(), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + job.AddInstance("hello"); + job.AddInstance("world"); + ASSERT_EQ(2, job.GetCommandsCount()); + ASSERT_EQ(2, job.GetInstancesCount()); + + job.Start(); + ASSERT_EQ(0, job.GetPosition()); + ASSERT_FALSE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(1, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(2, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step(), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + ASSERT_EQ(0, job.GetInstancesCount()); + ASSERT_EQ(0, job.GetCommandsCount()); + job.AddTrailingStep(); + ASSERT_EQ(0, job.GetInstancesCount()); + ASSERT_EQ(1, job.GetCommandsCount()); + + job.Start(); // This adds the trailing step + ASSERT_EQ(0, job.GetPosition()); + ASSERT_TRUE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(1, job.GetPosition()); + ASSERT_TRUE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step(), OrthancException); + } + + { + Json::Value s; + + DummyInstancesJob job; + job.AddInstance("hello"); + ASSERT_EQ(1, job.GetInstancesCount()); + ASSERT_EQ(1, job.GetCommandsCount()); + job.AddTrailingStep(); + ASSERT_EQ(1, job.GetInstancesCount()); + ASSERT_EQ(2, job.GetCommandsCount()); + + job.Start(); + ASSERT_EQ(2, job.GetCommandsCount()); + ASSERT_EQ(0, job.GetPosition()); + ASSERT_TRUE(job.HasTrailingStep()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode()); + ASSERT_EQ(1, job.GetPosition()); + ASSERT_FALSE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_EQ(JobStepCode_Success, job.Step().GetCode()); + ASSERT_EQ(2, job.GetPosition()); + ASSERT_TRUE(job.IsTrailingStepDone()); + + { + DummyUnserializer unserializer; + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + } + + ASSERT_THROW(job.Step(), OrthancException); + } +} + + +TEST(JobsSerialization, RemoteModalityParameters) +{ + Json::Value s; + + { + RemoteModalityParameters modality; + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + modality.Serialize(s, false); + ASSERT_EQ(Json::arrayValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + ASSERT_EQ("ORTHANC", modality.GetApplicationEntityTitle()); + ASSERT_EQ("127.0.0.1", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Generic, 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)); + } + + s = Json::nullValue; + + { + RemoteModalityParameters modality; + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + ASSERT_THROW(modality.SetPortNumber(0), OrthancException); + ASSERT_THROW(modality.SetPortNumber(65535), OrthancException); + modality.SetApplicationEntityTitle("HELLO"); + modality.SetHost("world"); + modality.SetPortNumber(45); + modality.SetManufacturer(ModalityManufacturer_Dcm4Chee); + modality.Serialize(s, true); + ASSERT_EQ(Json::objectValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + ASSERT_EQ("HELLO", modality.GetApplicationEntityTitle()); + ASSERT_EQ("world", modality.GetHost()); + ASSERT_EQ(45u, modality.GetPortNumber()); + ASSERT_EQ(ModalityManufacturer_Dcm4Chee, 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)); + } + + s["Port"] = "46"; + + { + RemoteModalityParameters modality(s); + ASSERT_EQ(46u, modality.GetPortNumber()); + } + + s["Port"] = -1; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + s["Port"] = 65535; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + s["Port"] = "nope"; ASSERT_THROW(RemoteModalityParameters m(s), OrthancException); + + std::set<DicomRequestType> operations; + operations.insert(DicomRequestType_Echo); + operations.insert(DicomRequestType_Find); + operations.insert(DicomRequestType_Get); + operations.insert(DicomRequestType_Move); + operations.insert(DicomRequestType_Store); + + ASSERT_EQ(5u, operations.size()); + + for (std::set<DicomRequestType>::const_iterator + it = operations.begin(); it != operations.end(); ++it) + { + { + RemoteModalityParameters modality; + modality.SetRequestAllowed(*it, false); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + + modality.Serialize(s, false); + ASSERT_EQ(Json::objectValue, s.type()); + } + + { + RemoteModalityParameters modality(s); + + ASSERT_FALSE(modality.IsRequestAllowed(*it)); + + for (std::set<DicomRequestType>::const_iterator + it2 = operations.begin(); it2 != operations.end(); ++it2) + { + if (*it2 != *it) + { + ASSERT_TRUE(modality.IsRequestAllowed(*it2)); + } + } + } + } +}
--- a/UnitTestsSources/PluginsTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/PluginsTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,12 +34,13 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../../Core/OrthancException.h" #include "../Plugins/Engine/PluginsManager.h" using namespace Orthanc; -#if ORTHANC_PLUGINS_ENABLED == 1 +#if ORTHANC_ENABLE_PLUGINS == 1 TEST(SharedLibrary, Enumerations) { @@ -57,15 +59,25 @@ ASSERT_TRUE(l.HasFunction("GetVersionExW")); ASSERT_FALSE(l.HasFunction("world")); -#elif defined(__linux) || defined(__FreeBSD_kernel__) +#elif defined(__LSB_VERSION__) + // For Linux Standard Base, we use a low-level shared library coming + // with glibc: + // http://www.linuxfromscratch.org/lfs/view/6.5/chapter06/glibc.html + SharedLibrary l("libSegFault.so"); + ASSERT_THROW(l.GetFunction("world"), OrthancException); + ASSERT_TRUE(l.GetFunction("_init") != NULL); + ASSERT_TRUE(l.HasFunction("_init")); + ASSERT_FALSE(l.HasFunction("world")); + +#elif defined(__linux__) || defined(__FreeBSD_kernel__) SharedLibrary l("libdl.so"); ASSERT_THROW(l.GetFunction("world"), OrthancException); ASSERT_TRUE(l.GetFunction("dlopen") != NULL); ASSERT_TRUE(l.HasFunction("dlclose")); ASSERT_FALSE(l.HasFunction("world")); -#elif defined(__FreeBSD__) - // dlopen() in FreeBSD is supplied by libc, libc.so is +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + // dlopen() in FreeBSD/OpenBSD is supplied by libc, libc.so is // a ldscript, so we can't actually use it. Use thread // library instead - if it works - dlopen() is good. SharedLibrary l("libpthread.so");
--- a/UnitTestsSources/PrecompiledHeadersUnitTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/PrecompiledHeadersUnitTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/PrecompiledHeadersUnitTests.h Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * 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 Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/RestApiTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,15 +35,18 @@ #include "gtest/gtest.h" #include <ctype.h> +#include <boost/lexical_cast.hpp> +#include <algorithm> #include "../Core/ChunkedBuffer.h" #include "../Core/HttpClient.h" #include "../Core/Logging.h" +#include "../Core/SystemToolbox.h" #include "../Core/RestApi/RestApi.h" -#include "../Core/Uuid.h" #include "../Core/OrthancException.h" #include "../Core/Compression/ZlibCompressor.h" #include "../Core/RestApi/RestApiHierarchy.h" +#include "../Core/HttpServer/HttpContentNegociation.h" using namespace Orthanc; @@ -62,15 +66,22 @@ ASSERT_FALSE(c.IsVerbose()); #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 + // The "http://www.orthanc-server.com/downloads/third-party/" does + // not automatically redirect to HTTPS, so we cas use it even if the + // OpenSSL/HTTPS support is disabled in curl + const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/"; + Json::Value v; - c.SetUrl("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/Configuration.json"); + c.SetUrl(BASE + "Product.json"); + c.Apply(v); - ASSERT_TRUE(v.isMember("StorageDirectory")); + ASSERT_TRUE(v.type() == Json::objectValue); + ASSERT_TRUE(v.isMember("Description")); #endif } -#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 && ORTHANC_SSL_ENABLED == 1 +#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 && ORTHANC_ENABLE_SSL == 1 /** The HTTPS CA certificates for BitBucket were extracted as follows: @@ -97,12 +108,12 @@ TEST(HttpClient, Ssl) { - Toolbox::WriteFile(BITBUCKET_CERTIFICATES, "UnitTestsResults/bitbucket.cert"); + SystemToolbox::WriteFile(BITBUCKET_CERTIFICATES, "UnitTestsResults/bitbucket.cert"); /*{ std::string s; - Toolbox::ReadFile(s, "/usr/share/ca-certificates/mozilla/WoSign.crt"); - Toolbox::WriteFile(s, "UnitTestsResults/bitbucket.cert"); + SystemToolbox::ReadFile(s, "/usr/share/ca-certificates/mozilla/WoSign.crt"); + SystemToolbox::WriteFile(s, "UnitTestsResults/bitbucket.cert"); }*/ HttpClient c; @@ -277,7 +288,7 @@ const IHttpHandler::Arguments& components, const UriComponents& trailing) { - return resource.Handle(*reinterpret_cast<RestApiGetCall*>(NULL)); + return resource.Handle(*(RestApiGetCall*) NULL); } }; } @@ -335,3 +346,300 @@ ASSERT_TRUE(HandleGet(root, "/hello2/a/b")); ASSERT_EQ(testValue, 4); } + + + + + +namespace +{ + class AcceptHandler : public Orthanc::HttpContentNegociation::IHandler + { + private: + std::string type_; + std::string subtype_; + + public: + AcceptHandler() + { + Reset(); + } + + void Reset() + { + Handle("nope", "nope"); + } + + const std::string& GetType() const + { + return type_; + } + + const std::string& GetSubType() const + { + return subtype_; + } + + virtual void Handle(const std::string& type, + const std::string& subtype) + { + type_ = type; + subtype_ = subtype; + } + }; +} + + +TEST(RestApi, HttpContentNegociation) +{ + // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 + + AcceptHandler h; + + { + Orthanc::HttpContentNegociation d; + d.Register("audio/mp3", h); + d.Register("audio/basic", h); + + ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic")); + ASSERT_EQ("audio", h.GetType()); + ASSERT_EQ("basic", h.GetSubType()); + + ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope")); + ASSERT_EQ("audio", h.GetType()); + ASSERT_EQ("mp3", h.GetSubType()); + + ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf")); + + ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf")); + ASSERT_EQ("audio", h.GetType()); + } + + // "This would be interpreted as "text/html and text/x-c are the + // preferred media types, but if they do not exist, then send the + // text/x-dvi entity, and if that does not exist, send the + // text/plain entity."" + const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"; + + { + Orthanc::HttpContentNegociation d; + d.Register("text/plain", h); + d.Register("text/html", h); + d.Register("text/x-dvi", h); + ASSERT_TRUE(d.Apply(T1)); + ASSERT_EQ("text", h.GetType()); + ASSERT_EQ("html", h.GetSubType()); + } + + { + Orthanc::HttpContentNegociation d; + d.Register("text/plain", h); + d.Register("text/x-dvi", h); + d.Register("text/x-c", h); + ASSERT_TRUE(d.Apply(T1)); + ASSERT_EQ("text", h.GetType()); + ASSERT_EQ("x-c", h.GetSubType()); + } + + { + Orthanc::HttpContentNegociation d; + d.Register("text/plain", h); + d.Register("text/x-dvi", h); + d.Register("text/x-c", h); + d.Register("text/html", h); + ASSERT_TRUE(d.Apply(T1)); + ASSERT_EQ("text", h.GetType()); + ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html"); + } + + { + Orthanc::HttpContentNegociation d; + d.Register("text/plain", h); + d.Register("text/x-dvi", h); + ASSERT_TRUE(d.Apply(T1)); + ASSERT_EQ("text", h.GetType()); + ASSERT_EQ("x-dvi", h.GetSubType()); + } + + { + Orthanc::HttpContentNegociation d; + d.Register("text/plain", h); + ASSERT_TRUE(d.Apply(T1)); + ASSERT_EQ("text", h.GetType()); + ASSERT_EQ("plain", h.GetSubType()); + } +} + + +TEST(WebServiceParameters, Serialization) +{ + { + Json::Value v = Json::arrayValue; + v.append("http://localhost:8042/"); + + WebServiceParameters p(v); + ASSERT_FALSE(p.IsAdvancedFormatNeeded()); + + Json::Value v2; + p.Serialize(v2, false, true); + ASSERT_EQ(v, v2); + + WebServiceParameters p2(v2); + ASSERT_EQ("http://localhost:8042/", p2.GetUrl()); + ASSERT_TRUE(p2.GetUsername().empty()); + ASSERT_TRUE(p2.GetPassword().empty()); + ASSERT_TRUE(p2.GetCertificateFile().empty()); + ASSERT_TRUE(p2.GetCertificateKeyFile().empty()); + ASSERT_TRUE(p2.GetCertificateKeyPassword().empty()); + ASSERT_FALSE(p2.IsPkcs11Enabled()); + } + + { + Json::Value v = Json::arrayValue; + v.append("http://localhost:8042/"); + v.append("user"); + v.append("pass"); + + WebServiceParameters p(v); + ASSERT_FALSE(p.IsAdvancedFormatNeeded()); + ASSERT_EQ("http://localhost:8042/", p.GetUrl()); + ASSERT_EQ("user", p.GetUsername()); + ASSERT_EQ("pass", p.GetPassword()); + ASSERT_TRUE(p.GetCertificateFile().empty()); + ASSERT_TRUE(p.GetCertificateKeyFile().empty()); + ASSERT_TRUE(p.GetCertificateKeyPassword().empty()); + ASSERT_FALSE(p.IsPkcs11Enabled()); + + Json::Value v2; + p.Serialize(v2, false, true); + ASSERT_EQ(v, v2); + + p.Serialize(v2, false, false /* no password */); + WebServiceParameters p2(v2); + ASSERT_EQ(Json::arrayValue, v2.type()); + ASSERT_EQ(3u, v2.size()); + ASSERT_EQ("http://localhost:8042/", v2[0u].asString()); + ASSERT_EQ("user", v2[1u].asString()); + ASSERT_TRUE(v2[2u].asString().empty()); + } + + { + Json::Value v = Json::arrayValue; + v.append("http://localhost:8042/"); + + WebServiceParameters p(v); + ASSERT_FALSE(p.IsAdvancedFormatNeeded()); + p.SetPkcs11Enabled(true); + ASSERT_TRUE(p.IsAdvancedFormatNeeded()); + + Json::Value v2; + p.Serialize(v2, false, true); + WebServiceParameters p2(v2); + + ASSERT_EQ(Json::objectValue, v2.type()); + ASSERT_EQ(3u, v2.size()); + ASSERT_EQ("http://localhost:8042/", v2["Url"].asString()); + ASSERT_TRUE(v2["Pkcs11"].asBool()); + ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type()); + ASSERT_EQ(0u, v2["HttpHeaders"].size()); + } + + { + Json::Value v = Json::arrayValue; + v.append("http://localhost:8042/"); + + WebServiceParameters p(v); + ASSERT_FALSE(p.IsAdvancedFormatNeeded()); + p.SetClientCertificate("a", "b", "c"); + ASSERT_TRUE(p.IsAdvancedFormatNeeded()); + + Json::Value v2; + p.Serialize(v2, false, true); + WebServiceParameters p2(v2); + + ASSERT_EQ(Json::objectValue, v2.type()); + ASSERT_EQ(6u, v2.size()); + ASSERT_EQ("http://localhost:8042/", v2["Url"].asString()); + ASSERT_EQ("a", v2["CertificateFile"].asString()); + ASSERT_EQ("b", v2["CertificateKeyFile"].asString()); + ASSERT_EQ("c", v2["CertificateKeyPassword"].asString()); + ASSERT_FALSE(v2["Pkcs11"].asBool()); + ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type()); + ASSERT_EQ(0u, v2["HttpHeaders"].size()); + } + + { + Json::Value v = Json::arrayValue; + v.append("http://localhost:8042/"); + + WebServiceParameters p(v); + ASSERT_FALSE(p.IsAdvancedFormatNeeded()); + p.AddHttpHeader("a", "b"); + p.AddHttpHeader("c", "d"); + ASSERT_TRUE(p.IsAdvancedFormatNeeded()); + + Json::Value v2; + p.Serialize(v2, false, true); + WebServiceParameters p2(v2); + + ASSERT_EQ(Json::objectValue, v2.type()); + ASSERT_EQ(3u, v2.size()); + ASSERT_EQ("http://localhost:8042/", v2["Url"].asString()); + ASSERT_FALSE(v2["Pkcs11"].asBool()); + ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type()); + ASSERT_EQ(2u, v2["HttpHeaders"].size()); + ASSERT_EQ("b", v2["HttpHeaders"]["a"].asString()); + ASSERT_EQ("d", v2["HttpHeaders"]["c"].asString()); + + std::set<std::string> a; + p2.ListHttpHeaders(a); + ASSERT_EQ(2u, a.size()); + ASSERT_TRUE(a.find("a") != a.end()); + ASSERT_TRUE(a.find("c") != a.end()); + + std::string s; + ASSERT_TRUE(p2.LookupHttpHeader(s, "a")); ASSERT_EQ("b", s); + ASSERT_TRUE(p2.LookupHttpHeader(s, "c")); ASSERT_EQ("d", s); + ASSERT_FALSE(p2.LookupHttpHeader(s, "nope")); + } +} + + +TEST(WebServiceParameters, UserProperties) +{ + Json::Value v = Json::nullValue; + + { + WebServiceParameters p; + p.SetUrl("http://localhost:8042/"); + ASSERT_FALSE(p.IsAdvancedFormatNeeded()); + + ASSERT_THROW(p.AddUserProperty("Url", "nope"), OrthancException); + p.AddUserProperty("Hello", "world"); + p.AddUserProperty("a", "b"); + ASSERT_TRUE(p.IsAdvancedFormatNeeded()); + + p.Serialize(v, false, true); + + p.ClearUserProperties(); + ASSERT_FALSE(p.IsAdvancedFormatNeeded()); + } + + { + WebServiceParameters p(v); + ASSERT_TRUE(p.IsAdvancedFormatNeeded()); + ASSERT_TRUE(p.GetHttpHeaders().empty()); + + std::set<std::string> tmp; + p.ListUserProperties(tmp); + ASSERT_EQ(2u, tmp.size()); + ASSERT_TRUE(tmp.find("a") != tmp.end()); + ASSERT_TRUE(tmp.find("Hello") != tmp.end()); + ASSERT_TRUE(tmp.find("hello") == tmp.end()); + + std::string s; + ASSERT_TRUE(p.LookupUserProperty(s, "a")); ASSERT_TRUE(s == "b"); + ASSERT_TRUE(p.LookupUserProperty(s, "Hello")); ASSERT_TRUE(s == "world"); + ASSERT_FALSE(p.LookupUserProperty(s, "hello")); + } +}
--- a/UnitTestsSources/SQLiteChromiumTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/SQLiteChromiumTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,35 +51,38 @@ ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc ********************************************************************/ -class SQLConnectionTest : public testing::Test +namespace { -public: - SQLConnectionTest() - { - } - - virtual ~SQLConnectionTest() - { - } - - virtual void SetUp() + class SQLConnectionTest : public testing::Test { - db_.OpenInMemory(); - } + public: + SQLConnectionTest() + { + } - virtual void TearDown() - { - db_.Close(); - } + virtual ~SQLConnectionTest() + { + } + + virtual void SetUp() + { + db_.OpenInMemory(); + } - Connection& db() - { - return db_; - } + virtual void TearDown() + { + db_.Close(); + } -private: - Connection db_; -}; + Connection& db() + { + return db_; + } + + private: + Connection db_; + }; +} @@ -265,23 +269,26 @@ ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc ********************************************************************/ -class SQLTransactionTest : public SQLConnectionTest +namespace { -public: - virtual void SetUp() + class SQLTransactionTest : public SQLConnectionTest { - SQLConnectionTest::SetUp(); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - } + public: + virtual void SetUp() + { + SQLConnectionTest::SetUp(); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + } - // Returns the number of rows in table "foo". - int CountFoo() - { - Statement count(db(), "SELECT count(*) FROM foo"); - count.Step(); - return count.ColumnInt(0); - } -}; + // Returns the number of rows in table "foo". + int CountFoo() + { + Statement count(db(), "SELECT count(*) FROM foo"); + count.Step(); + return count.ColumnInt(0); + } + }; +} TEST_F(SQLTransactionTest, Commit) {
--- a/UnitTestsSources/SQLiteTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/SQLiteTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +34,7 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../Core/Toolbox.h" +#include "../Core/SystemToolbox.h" #include "../Core/SQLite/Connection.h" #include "../Core/SQLite/Statement.h" #include "../Core/SQLite/Transaction.h" @@ -58,7 +59,7 @@ TEST(SQLite, Connection) { - Toolbox::RemoveFile("UnitTestsResults/coucou"); + SystemToolbox::RemoveFile("UnitTestsResults/coucou"); SQLite::Connection c; c.Open("UnitTestsResults/coucou"); c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)");
--- a/UnitTestsSources/ServerIndexTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/ServerIndexTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 +35,8 @@ #include "gtest/gtest.h" #include "../Core/FileStorage/FilesystemStorage.h" +#include "../Core/FileStorage/MemoryStorageArea.h" #include "../Core/Logging.h" -#include "../Core/Uuid.h" #include "../OrthancServer/DatabaseWrapper.h" #include "../OrthancServer/ServerContext.h" #include "../OrthancServer/ServerIndex.h" @@ -671,11 +672,13 @@ { const std::string path = "UnitTestsStorage"; - Toolbox::RemoveFile(path + "/index"); + SystemToolbox::RemoveFile(path + "/index"); FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory db.Open(); - ServerContext context(db, storage); + ServerContext context(db, storage, true /* running unit tests */); + context.SetupJobsEngine(true, false); + ServerIndex& index = context.GetIndex(); ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); @@ -769,11 +772,12 @@ { const std::string path = "UnitTestsStorage"; - Toolbox::RemoveFile(path + "/index"); + SystemToolbox::RemoveFile(path + "/index"); FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory db.Open(); - ServerContext context(db, storage); + ServerContext context(db, storage, true /* running unit tests */); + context.SetupJobsEngine(true, false); ServerIndex& index = context.GetIndex(); index.SetMaximumStorageSize(10); @@ -790,17 +794,26 @@ { std::string id = boost::lexical_cast<std::string>(i); DicomMap instance; - instance.SetValue(DICOM_TAG_PATIENT_ID, "patient-" + id); - instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study-" + id); - instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series-" + id); - instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id); + instance.SetValue(DICOM_TAG_PATIENT_ID, "patient-" + id, false); + instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study-" + id, false); + instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series-" + id, false); + instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id, false); + instance.SetValue(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false); // CR image std::map<MetadataType, std::string> instanceMetadata; - ServerIndex::MetadataMap metadata; - ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, instance, attachments, "", metadata)); - ASSERT_EQ(2u, instanceMetadata.size()); + DicomInstanceToStore toStore; + toStore.SetSummary(instance); + ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments)); + ASSERT_EQ(5u, instanceMetadata.size()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_RemoteAet) != instanceMetadata.end()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end()); + ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_TransferSyntax) != instanceMetadata.end()); + ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_SopClassUid) != instanceMetadata.end()); + + // By default, an Explicit VR Little Endian is used by Orthanc + ASSERT_EQ("1.2.840.10008.1.2.1", instanceMetadata[MetadataType_Instance_TransferSyntax]); + + ASSERT_EQ("1.2.840.10008.5.1.4.1.1.1", instanceMetadata[MetadataType_Instance_SopClassUid]); DicomInstanceHasher hasher(instance); ids.push_back(hasher.HashPatient()); @@ -823,8 +836,144 @@ } // Because the DB is in memory, the SQLite index must not have been created - ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException); + ASSERT_FALSE(SystemToolbox::IsRegularFile(path + "/index")); context.Stop(); db.Close(); } + + +TEST(LookupIdentifierQuery, NormalizeIdentifier) +{ + ASSERT_EQ("H^L.LO", ServerToolbox::NormalizeIdentifier(" Hé^l.LO %_ ")); + ASSERT_EQ("1.2.840.113619.2.176.2025", ServerToolbox::NormalizeIdentifier(" 1.2.840.113619.2.176.2025 ")); +} + + +TEST(ServerIndex, Overwrite) +{ + for (unsigned int i = 0; i < 2; i++) + { + bool overwrite = (i == 0); + + MemoryStorageArea storage; + DatabaseWrapper db; // The SQLite DB is in memory + db.Open(); + ServerContext context(db, storage, true /* running unit tests */); + context.SetupJobsEngine(true, false); + context.SetCompressionEnabled(true); + + DicomMap instance; + instance.SetValue(DICOM_TAG_PATIENT_ID, "patient", false); + instance.SetValue(DICOM_TAG_PATIENT_NAME, "name", false); + instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study", false); + instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series", false); + instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "sop", false); + instance.SetValue(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false); // CR image + + DicomInstanceHasher hasher(instance); + std::string id = hasher.HashInstance(); + context.GetIndex().SetOverwriteInstances(overwrite); + + Json::Value tmp; + context.GetIndex().ComputeStatistics(tmp); + ASSERT_EQ(0, tmp["CountInstances"].asInt()); + ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString())); + + { + DicomInstanceToStore toStore; + toStore.SetSummary(instance); + toStore.SetOrigin(DicomInstanceOrigin::FromPlugins()); + + std::string id2; + ASSERT_EQ(StoreStatus_Success, context.Store(id2, toStore)); + ASSERT_EQ(id, id2); + } + + FileInfo dicom1, json1; + ASSERT_TRUE(context.GetIndex().LookupAttachment(dicom1, id, FileContentType_Dicom)); + ASSERT_TRUE(context.GetIndex().LookupAttachment(json1, id, FileContentType_DicomAsJson)); + + context.GetIndex().ComputeStatistics(tmp); + ASSERT_EQ(1, tmp["CountInstances"].asInt()); + ASSERT_EQ(dicom1.GetCompressedSize() + json1.GetCompressedSize(), + boost::lexical_cast<size_t>(tmp["TotalDiskSize"].asString())); + ASSERT_EQ(dicom1.GetUncompressedSize() + json1.GetUncompressedSize(), + boost::lexical_cast<size_t>(tmp["TotalUncompressedSize"].asString())); + + context.ReadDicomAsJson(tmp, id); + ASSERT_EQ("name", tmp["0010,0010"]["Value"].asString()); + + { + ServerContext::DicomCacheLocker locker(context, id); + std::string tmp; + locker.GetDicom().GetTagValue(tmp, DICOM_TAG_PATIENT_NAME); + ASSERT_EQ("name", tmp); + } + + { + DicomMap instance2; + instance2.Assign(instance); + instance2.SetValue(DICOM_TAG_PATIENT_NAME, "overwritten", false); + + DicomInstanceToStore toStore; + toStore.SetSummary(instance2); + toStore.SetOrigin(DicomInstanceOrigin::FromPlugins()); + + std::string id2; + ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, context.Store(id2, toStore)); + ASSERT_EQ(id, id2); + } + + FileInfo dicom2, json2; + ASSERT_TRUE(context.GetIndex().LookupAttachment(dicom2, id, FileContentType_Dicom)); + ASSERT_TRUE(context.GetIndex().LookupAttachment(json2, id, FileContentType_DicomAsJson)); + + context.GetIndex().ComputeStatistics(tmp); + ASSERT_EQ(1, tmp["CountInstances"].asInt()); + ASSERT_EQ(dicom2.GetCompressedSize() + json2.GetCompressedSize(), + boost::lexical_cast<size_t>(tmp["TotalDiskSize"].asString())); + ASSERT_EQ(dicom2.GetUncompressedSize() + json2.GetUncompressedSize(), + boost::lexical_cast<size_t>(tmp["TotalUncompressedSize"].asString())); + + if (overwrite) + { + ASSERT_NE(dicom1.GetUuid(), dicom2.GetUuid()); + ASSERT_NE(json1.GetUuid(), json2.GetUuid()); + ASSERT_NE(dicom1.GetUncompressedSize(), dicom2.GetUncompressedSize()); + ASSERT_NE(json1.GetUncompressedSize(), json2.GetUncompressedSize()); + + context.ReadDicomAsJson(tmp, id); + ASSERT_EQ("overwritten", tmp["0010,0010"]["Value"].asString()); + + { + ServerContext::DicomCacheLocker locker(context, id); + std::string tmp; + locker.GetDicom().GetTagValue(tmp, DICOM_TAG_PATIENT_NAME); + ASSERT_EQ("overwritten", tmp); + } + } + else + { + ASSERT_EQ(dicom1.GetUuid(), dicom2.GetUuid()); + ASSERT_EQ(json1.GetUuid(), json2.GetUuid()); + ASSERT_EQ(dicom1.GetUncompressedSize(), dicom2.GetUncompressedSize()); + ASSERT_EQ(json1.GetUncompressedSize(), json2.GetUncompressedSize()); + + context.ReadDicomAsJson(tmp, id); + ASSERT_EQ("name", tmp["0010,0010"]["Value"].asString()); + + { + ServerContext::DicomCacheLocker locker(context, id); + std::string tmp; + locker.GetDicom().GetTagValue(tmp, DICOM_TAG_PATIENT_NAME); + ASSERT_EQ("name", tmp); + } + } + + context.Stop(); + db.Close(); + } +} + +
--- a/UnitTestsSources/StreamTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/StreamTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,9 +34,9 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/SystemToolbox.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" -#include "../Core/Uuid.h" #include "../Core/HttpServer/BufferHttpSender.h" #include "../Core/HttpServer/FilesystemHttpSender.h" #include "../Core/HttpServer/HttpStreamTranscoder.h" @@ -74,7 +75,7 @@ std::string uncompressed; IBufferCompressor::Uncompress(uncompressed, c, compressed); - ASSERT_EQ(0, uncompressed.size()); + ASSERT_TRUE(uncompressed.empty()); } @@ -107,7 +108,7 @@ std::string uncompressed; IBufferCompressor::Uncompress(uncompressed, c, compressed); - ASSERT_EQ(0, uncompressed.size()); + ASSERT_TRUE(uncompressed.empty()); } @@ -154,6 +155,7 @@ ZlibCompressor c; IBufferCompressor::Compress(compressed, c, s); + ASSERT_FALSE(compressed.empty()); compressed[compressed.size() - 1] = 'a'; std::string u; @@ -172,7 +174,7 @@ std::string uncompressed; IBufferCompressor::Uncompress(uncompressed, c, compressed); - ASSERT_EQ(0u, uncompressed.size()); + ASSERT_TRUE(uncompressed.empty()); } @@ -232,14 +234,14 @@ std::string t; { - Toolbox::WriteFile(s, path); + SystemToolbox::WriteFile(s, path); FilesystemHttpSender sender(path); ASSERT_TRUE(ReadAllStream(t, sender)); ASSERT_EQ(s, t); } { - Toolbox::WriteFile("", path); + SystemToolbox::WriteFile("", path); FilesystemHttpSender sender(path); ASSERT_TRUE(ReadAllStream(t, sender)); ASSERT_EQ(0u, t.size());
--- a/UnitTestsSources/UnitTestsMain.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/UnitTestsMain.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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,8 +42,8 @@ #include "../Core/HttpServer/HttpToolbox.h" #include "../Core/Logging.h" #include "../Core/OrthancException.h" +#include "../Core/TemporaryFile.h" #include "../Core/Toolbox.h" -#include "../Core/Uuid.h" #include "../OrthancServer/OrthancInitialization.h" @@ -364,12 +365,18 @@ std::string decoded; Toolbox::DecodeBase64(decoded, hello); ASSERT_EQ("Hello world", decoded); + + // Invalid character + ASSERT_THROW(Toolbox::DecodeBase64(decoded, "?"), OrthancException); + + // All the allowed characters + Toolbox::DecodeBase64(decoded, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="); } TEST(Toolbox, PathToExecutable) { - printf("[%s]\n", Toolbox::GetPathToExecutable().c_str()); - printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str()); + printf("[%s]\n", SystemToolbox::GetPathToExecutable().c_str()); + printf("[%s]\n", SystemToolbox::GetDirectoryOfExecutable().c_str()); } TEST(Toolbox, StripSpaces) @@ -410,10 +417,6 @@ // This is a Latin-1 test string const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 }; - /*FILE* f = fopen("/tmp/tutu", "w"); - fwrite(&data[0], 9, 1, f); - fclose(f);*/ - std::string s((char*) &data[0], 10); ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s)); @@ -458,7 +461,21 @@ } -#if defined(__linux) +TEST(Toolbox, IsAsciiString) +{ + std::string s = "Hello 12 /"; + ASSERT_EQ(10u, s.size()); + ASSERT_TRUE(Toolbox::IsAsciiString(s)); + ASSERT_TRUE(Toolbox::IsAsciiString(s.c_str(), 10)); + ASSERT_FALSE(Toolbox::IsAsciiString(s.c_str(), 11)); // Taking the trailing hidden '\0' + + s[2] = '\0'; + ASSERT_EQ(10u, s.size()); + ASSERT_FALSE(Toolbox::IsAsciiString(s)); +} + + +#if defined(__linux__) TEST(OrthancInitialization, AbsoluteDirectory) { ASSERT_EQ("/tmp/hello", Configuration::InterpretRelativePath("/tmp", "hello")); @@ -524,11 +541,17 @@ ASSERT_EQ(2047, StringToMetadata("Ceci est un test")); ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("Generic"))); + 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("MedInria", EnumerationToString(StringToModalityManufacturer("MedInria"))); ASSERT_STREQ("Dcm4Chee", EnumerationToString(StringToModalityManufacturer("Dcm4Chee"))); - ASSERT_STREQ("SyngoVia", EnumerationToString(StringToModalityManufacturer("SyngoVia"))); + ASSERT_STREQ("Vitrea", EnumerationToString(StringToModalityManufacturer("Vitrea"))); + // 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("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("SyngoVia"))); + ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("AgfaImpax"))); } @@ -538,7 +561,7 @@ std::string path; { - Toolbox::TemporaryFile tmp; + TemporaryFile tmp; path = tmp.GetPath(); std::string s; @@ -547,18 +570,28 @@ s.append("World"); ASSERT_EQ(11u, s.size()); - Toolbox::WriteFile(s, path.c_str()); + SystemToolbox::WriteFile(s, path.c_str()); std::string t; - Toolbox::ReadFile(t, path.c_str()); + SystemToolbox::ReadFile(t, path.c_str()); ASSERT_EQ(11u, t.size()); ASSERT_EQ(0, t[5]); ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size())); + + std::string h; + ASSERT_EQ(true, SystemToolbox::ReadHeader(h, path.c_str(), 1)); + ASSERT_EQ(1u, h.size()); + ASSERT_EQ('H', h[0]); + ASSERT_TRUE(SystemToolbox::ReadHeader(h, path.c_str(), 0)); + ASSERT_EQ(0u, h.size()); + ASSERT_FALSE(SystemToolbox::ReadHeader(h, path.c_str(), 32)); + ASSERT_EQ(11u, h.size()); + ASSERT_EQ(0, memcmp(s.c_str(), h.c_str(), s.size())); } std::string u; - ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException); + ASSERT_THROW(SystemToolbox::ReadFile(u, path.c_str()), OrthancException); } @@ -615,11 +648,48 @@ ASSERT_EQ(ResourceType_Instance, StringToResourceType(EnumerationToString(ResourceType_Instance))); ASSERT_EQ(ImageFormat_Png, StringToImageFormat(EnumerationToString(ImageFormat_Png))); + + ASSERT_EQ(PhotometricInterpretation_ARGB, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_ARGB))); + ASSERT_EQ(PhotometricInterpretation_CMYK, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_CMYK))); + ASSERT_EQ(PhotometricInterpretation_HSV, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_HSV))); + ASSERT_EQ(PhotometricInterpretation_Monochrome1, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Monochrome1))); + ASSERT_EQ(PhotometricInterpretation_Monochrome2, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Monochrome2))); + ASSERT_EQ(PhotometricInterpretation_Palette, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Palette))); + ASSERT_EQ(PhotometricInterpretation_RGB, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_RGB))); + ASSERT_EQ(PhotometricInterpretation_YBRFull, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRFull))); + ASSERT_EQ(PhotometricInterpretation_YBRFull422, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRFull422))); + ASSERT_EQ(PhotometricInterpretation_YBRPartial420, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRPartial420))); + ASSERT_EQ(PhotometricInterpretation_YBRPartial422, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRPartial422))); + ASSERT_EQ(PhotometricInterpretation_YBR_ICT, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBR_ICT))); + ASSERT_EQ(PhotometricInterpretation_YBR_RCT, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBR_RCT))); + + ASSERT_STREQ("Unknown", EnumerationToString(PhotometricInterpretation_Unknown)); + ASSERT_THROW(StringToPhotometricInterpretation("Unknown"), OrthancException); + + ASSERT_EQ(DicomVersion_2008, StringToDicomVersion(EnumerationToString(DicomVersion_2008))); + ASSERT_EQ(DicomVersion_2017c, StringToDicomVersion(EnumerationToString(DicomVersion_2017c))); + + for (int i = static_cast<int>(ValueRepresentation_ApplicationEntity); + i < static_cast<int>(ValueRepresentation_NotSupported); i += 1) + { + ValueRepresentation vr = static_cast<ValueRepresentation>(i); + ASSERT_EQ(vr, StringToValueRepresentation(EnumerationToString(vr), true)); + } + + ASSERT_THROW(StringToValueRepresentation("nope", true), OrthancException); + + ASSERT_EQ(JobState_Pending, StringToJobState(EnumerationToString(JobState_Pending))); + ASSERT_EQ(JobState_Running, StringToJobState(EnumerationToString(JobState_Running))); + ASSERT_EQ(JobState_Success, StringToJobState(EnumerationToString(JobState_Success))); + ASSERT_EQ(JobState_Failure, StringToJobState(EnumerationToString(JobState_Failure))); + ASSERT_EQ(JobState_Paused, StringToJobState(EnumerationToString(JobState_Paused))); + ASSERT_EQ(JobState_Retry, StringToJobState(EnumerationToString(JobState_Retry))); + ASSERT_THROW(StringToJobState("nope"), OrthancException); } -#if defined(__linux) +#if defined(__linux__) || defined(__OpenBSD__) #include <endian.h> #elif defined(__FreeBSD__) #include <machine/endian.h> @@ -639,12 +709,24 @@ #if defined(_WIN32) || defined(__APPLE__) ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); + + /** + * FreeBSD. + **/ + +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +# if _BYTE_ORDER == _BIG_ENDIAN + ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness()); +# else // _LITTLE_ENDIAN + ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); +# endif + /** * Linux. **/ -#elif defined(__linux) || defined(__FreeBSD_kernel__) +#elif defined(__linux__) || defined(__FreeBSD_kernel__) #if !defined(__BYTE_ORDER) # error Support your platform here @@ -656,25 +738,209 @@ ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); # endif - - /** - * FreeBSD. - **/ - -#elif defined(__FreeBSD__) -# if _BYTE_ORDER == _BIG_ENDIAN - ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness()); -# else // _LITTLE_ENDIAN - ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); -# endif - #else #error Support your platform here #endif } -#if ORTHANC_PUGIXML_ENABLED == 1 +#include "../Core/Endianness.h" + +static void ASSERT_EQ16(uint16_t a, uint16_t b) +{ +#ifdef __MINGW32__ + // This cast solves a linking problem with MinGW + ASSERT_EQ(static_cast<unsigned int>(a), static_cast<unsigned int>(b)); +#else + ASSERT_EQ(a, b); +#endif +} + +static void ASSERT_NE16(uint16_t a, uint16_t b) +{ +#ifdef __MINGW32__ + // This cast solves a linking problem with MinGW + ASSERT_NE(static_cast<unsigned int>(a), static_cast<unsigned int>(b)); +#else + ASSERT_NE(a, b); +#endif +} + +static void ASSERT_EQ32(uint32_t a, uint32_t b) +{ +#ifdef __MINGW32__ + // This cast solves a linking problem with MinGW + ASSERT_EQ(static_cast<unsigned int>(a), static_cast<unsigned int>(b)); +#else + ASSERT_EQ(a, b); +#endif +} + +static void ASSERT_NE32(uint32_t a, uint32_t b) +{ +#ifdef __MINGW32__ + // This cast solves a linking problem with MinGW + ASSERT_NE(static_cast<unsigned int>(a), static_cast<unsigned int>(b)); +#else + ASSERT_NE(a, b); +#endif +} + +static void ASSERT_EQ64(uint64_t a, uint64_t b) +{ +#ifdef __MINGW32__ + // This cast solves a linking problem with MinGW + ASSERT_EQ(static_cast<unsigned int>(a), static_cast<unsigned int>(b)); +#else + ASSERT_EQ(a, b); +#endif +} + +static void ASSERT_NE64(uint64_t a, uint64_t b) +{ +#ifdef __MINGW32__ + // This cast solves a linking problem with MinGW + ASSERT_NE(static_cast<unsigned long long>(a), static_cast<unsigned long long>(b)); +#else + ASSERT_NE(a, b); +#endif +} + + + +TEST(Toolbox, EndiannessConversions16) +{ + Endianness e = Toolbox::DetectEndianness(); + + for (unsigned int i = 0; i < 65536; i += 17) + { + uint16_t v = static_cast<uint16_t>(i); + ASSERT_EQ16(v, be16toh(htobe16(v))); + ASSERT_EQ16(v, le16toh(htole16(v))); + + const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&v); + if (bytes[0] != bytes[1]) + { + ASSERT_NE16(v, le16toh(htobe16(v))); + ASSERT_NE16(v, be16toh(htole16(v))); + } + else + { + ASSERT_EQ16(v, le16toh(htobe16(v))); + ASSERT_EQ16(v, be16toh(htole16(v))); + } + + switch (e) + { + case Endianness_Little: + ASSERT_EQ16(v, htole16(v)); + if (bytes[0] != bytes[1]) + { + ASSERT_NE16(v, htobe16(v)); + } + else + { + ASSERT_EQ16(v, htobe16(v)); + } + break; + + case Endianness_Big: + ASSERT_EQ16(v, htobe16(v)); + if (bytes[0] != bytes[1]) + { + ASSERT_NE16(v, htole16(v)); + } + else + { + ASSERT_EQ16(v, htole16(v)); + } + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } +} + + +TEST(Toolbox, EndiannessConversions32) +{ + const uint32_t v = 0xff010203u; + const uint32_t r = 0x030201ffu; + ASSERT_EQ32(v, be32toh(htobe32(v))); + ASSERT_EQ32(v, le32toh(htole32(v))); + ASSERT_NE32(v, be32toh(htole32(v))); + ASSERT_NE32(v, le32toh(htobe32(v))); + + switch (Toolbox::DetectEndianness()) + { + case Endianness_Little: + ASSERT_EQ32(r, htobe32(v)); + ASSERT_EQ32(v, htole32(v)); + ASSERT_EQ32(r, be32toh(v)); + ASSERT_EQ32(v, le32toh(v)); + break; + + case Endianness_Big: + ASSERT_EQ32(v, htobe32(v)); + ASSERT_EQ32(r, htole32(v)); + ASSERT_EQ32(v, be32toh(v)); + ASSERT_EQ32(r, le32toh(v)); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } +} + + +TEST(Toolbox, EndiannessConversions64) +{ + const uint64_t v = 0xff01020304050607LL; + const uint64_t r = 0x07060504030201ffLL; + ASSERT_EQ64(v, be64toh(htobe64(v))); + ASSERT_EQ64(v, le64toh(htole64(v))); + ASSERT_NE64(v, be64toh(htole64(v))); + ASSERT_NE64(v, le64toh(htobe64(v))); + + switch (Toolbox::DetectEndianness()) + { + case Endianness_Little: + ASSERT_EQ64(r, htobe64(v)); + ASSERT_EQ64(v, htole64(v)); + ASSERT_EQ64(r, be64toh(v)); + ASSERT_EQ64(v, le64toh(v)); + break; + + case Endianness_Big: + ASSERT_EQ64(v, htobe64(v)); + ASSERT_EQ64(r, htole64(v)); + ASSERT_EQ64(v, be64toh(v)); + ASSERT_EQ64(r, le64toh(v)); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } +} + + +TEST(Toolbox, Now) +{ + LOG(WARNING) << "Local time: " << SystemToolbox::GetNowIsoString(false); + LOG(WARNING) << "Universal time: " << SystemToolbox::GetNowIsoString(true); + + std::string date, time; + SystemToolbox::GetNowDicom(date, time, false); + LOG(WARNING) << "Local DICOM time: [" << date << "] [" << time << "]"; + + SystemToolbox::GetNowDicom(date, time, true); + LOG(WARNING) << "Universal DICOM time: [" << date << "] [" << time << "]"; +} + + + +#if ORTHANC_ENABLE_PUGIXML == 1 TEST(Toolbox, Xml) { Json::Value a; @@ -699,7 +965,7 @@ args[0] = "Hello"; args[1] = "World"; - Toolbox::ExecuteSystemCommand("echo", args); + SystemToolbox::ExecuteSystemCommand("echo", args); } #endif @@ -729,12 +995,135 @@ } +TEST(Toolbox, UriEncode) +{ + std::string s; + + // Unreserved characters must not be modified + std::string t = "aAzZ09.-~_"; + Toolbox::UriEncode(s, t); + ASSERT_EQ(t, s); + + Toolbox::UriEncode(s, "!#$&'()*+,/:;=?@[]"); ASSERT_EQ("%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D", s); + Toolbox::UriEncode(s, "%"); ASSERT_EQ("%25", s); + + // Encode characters from UTF-8. This is the test string from the + // file "../Resources/EncodingTests.py" + Toolbox::UriEncode(s, "\x54\x65\x73\x74\xc3\xa9\xc3\xa4\xc3\xb6\xc3\xb2\xd0\x94\xce\x98\xc4\x9d\xd7\x93\xd8\xb5\xc4\xb7\xd1\x9b\xe0\xb9\x9b\xef\xbe\x88\xc4\xb0"); + ASSERT_EQ("Test%C3%A9%C3%A4%C3%B6%C3%B2%D0%94%CE%98%C4%9D%D7%93%D8%B5%C4%B7%D1%9B%E0%B9%9B%EF%BE%88%C4%B0", s); +} + + +TEST(Toolbox, AccessJson) +{ + Json::Value v = Json::arrayValue; + ASSERT_EQ("nope", Toolbox::GetJsonStringField(v, "hello", "nope")); + + v = Json::objectValue; + ASSERT_EQ("nope", Toolbox::GetJsonStringField(v, "hello", "nope")); + ASSERT_EQ(-10, Toolbox::GetJsonIntegerField(v, "hello", -10)); + ASSERT_EQ(10u, Toolbox::GetJsonUnsignedIntegerField(v, "hello", 10)); + ASSERT_TRUE(Toolbox::GetJsonBooleanField(v, "hello", true)); + + v["hello"] = "world"; + ASSERT_EQ("world", Toolbox::GetJsonStringField(v, "hello", "nope")); + ASSERT_THROW(Toolbox::GetJsonIntegerField(v, "hello", -10), OrthancException); + ASSERT_THROW(Toolbox::GetJsonUnsignedIntegerField(v, "hello", 10), OrthancException); + ASSERT_THROW(Toolbox::GetJsonBooleanField(v, "hello", true), OrthancException); + + v["hello"] = -42; + ASSERT_THROW(Toolbox::GetJsonStringField(v, "hello", "nope"), OrthancException); + ASSERT_EQ(-42, Toolbox::GetJsonIntegerField(v, "hello", -10)); + ASSERT_THROW(Toolbox::GetJsonUnsignedIntegerField(v, "hello", 10), OrthancException); + ASSERT_THROW(Toolbox::GetJsonBooleanField(v, "hello", true), OrthancException); + + v["hello"] = 42; + ASSERT_THROW(Toolbox::GetJsonStringField(v, "hello", "nope"), OrthancException); + ASSERT_EQ(42, Toolbox::GetJsonIntegerField(v, "hello", -10)); + ASSERT_EQ(42u, Toolbox::GetJsonUnsignedIntegerField(v, "hello", 10)); + ASSERT_THROW(Toolbox::GetJsonBooleanField(v, "hello", true), OrthancException); + + v["hello"] = false; + ASSERT_THROW(Toolbox::GetJsonStringField(v, "hello", "nope"), OrthancException); + ASSERT_THROW(Toolbox::GetJsonIntegerField(v, "hello", -10), OrthancException); + ASSERT_THROW(Toolbox::GetJsonUnsignedIntegerField(v, "hello", 10), OrthancException); + ASSERT_FALSE(Toolbox::GetJsonBooleanField(v, "hello", true)); +} + + +TEST(Toolbox, LinesIterator) +{ + std::string s; + + { + std::string content; + Toolbox::LinesIterator it(content); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\n\r"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\n Hello \n\nWorld\n\n"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\r Hello \r\rWorld\r\r"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\n\r Hello \n\r\n\rWorld\n\r\n\r"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\r\n Hello \r\n\r\nWorld\r\n\r\n"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } +} + + int main(int argc, char **argv) { Logging::Initialize(); Logging::EnableInfoLevel(true); Toolbox::DetectEndianness(); - Toolbox::MakeDirectory("UnitTestsResults"); + SystemToolbox::MakeDirectory("UnitTestsResults"); OrthancInitialize(); ::testing::InitGoogleTest(&argc, argv);
--- a/UnitTestsSources/VersionsTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/VersionsTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -43,8 +44,9 @@ #include <sqlite3.h> #include <lua.h> #include <jpeglib.h> +#include <iconv.h> -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 #include <openssl/opensslv.h> #endif @@ -97,23 +99,23 @@ TEST(Versions, ZlibStatic) { - ASSERT_STREQ("1.2.7", zlibVersion()); + ASSERT_STREQ("1.2.11", zlibVersion()); } TEST(Versions, BoostStatic) { - ASSERT_STREQ("1_58", BOOST_LIB_VERSION); + ASSERT_STREQ("1_67", BOOST_LIB_VERSION); } TEST(Versions, CurlStatic) { curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ("7.44.0", v->version); + ASSERT_STREQ("7.57.0", v->version); } TEST(Versions, PngStatic) { - ASSERT_EQ(10512, png_access_version_number()); + ASSERT_EQ(10512u, png_access_version_number()); ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); } @@ -130,7 +132,7 @@ // Check that SSL support is enabled when required bool curlSupportsSsl = (vinfo->features & CURL_VERSION_SSL) != 0; -#if ORTHANC_SSL_ENABLED == 0 +#if ORTHANC_ENABLE_SSL == 0 ASSERT_FALSE(curlSupportsSsl); #else ASSERT_TRUE(curlSupportsSsl); @@ -142,11 +144,18 @@ ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); } +TEST(Version, LibIconvStatic) +{ + static const int major = 1; + static const int minor = 15; + ASSERT_EQ((major << 8) + minor, _LIBICONV_VERSION); +} -#if ORTHANC_SSL_ENABLED == 1 + +#if ORTHANC_ENABLE_SSL == 1 TEST(Version, OpenSslStatic) { - ASSERT_EQ(0x1000204fL /* openssl-1.0.2d */, OPENSSL_VERSION_NUMBER); + ASSERT_EQ(0x100020ffL /* openssl-1.0.2o */, OPENSSL_VERSION_NUMBER); } #endif @@ -155,7 +164,13 @@ TEST(Version, JsonCpp) { - ASSERT_STREQ("0.10.5", JSONCPP_VERSION_STRING); +#if ORTHANC_LEGACY_JSONCPP == 1 + ASSERT_STREQ("0.10.6", JSONCPP_VERSION_STRING); +#elif ORTHANC_LEGACY_JSONCPP == 0 + ASSERT_STREQ("1.8.4", JSONCPP_VERSION_STRING); +#else +# error Macro ORTHANC_LEGACY_JSONCPP should be set +#endif } #endif
--- a/UnitTestsSources/ZipTests.cpp Thu Oct 29 11:25:45 2015 +0100 +++ b/UnitTestsSources/ZipTests.cpp Fri Oct 12 15:18:10 2018 +0200 @@ -1,7 +1,8 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as