Mercurial > hg > orthanc
changeset 947:c2c28dd17e87 query-retrieve
integration mainline -> query-retrieve
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 25 Jun 2014 12:09:38 +0200 |
parents | 67e6400fca03 (current diff) b3f6fb1130cd (diff) |
children | f894be6e7cc1 |
files | Core/DicomFormat/DicomMap.cpp Core/DicomFormat/DicomTag.h Core/FileFormats/PngReader.cpp Core/FileFormats/PngReader.h Core/FileFormats/PngWriter.cpp Core/FileFormats/PngWriter.h OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/ServerIndex.cpp OrthancServer/main.cpp |
diffstat | 169 files changed, 9749 insertions(+), 3262 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Apr 16 16:34:09 2014 +0200 +++ b/CMakeLists.txt Wed Jun 25 12:09:38 2014 +0200 @@ -18,6 +18,8 @@ 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_JPEG ON CACHE BOOL "Enable JPEG decompression") +SET(ENABLE_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression") # Advanced parameters to fine-tune linking against system libraries SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") @@ -44,6 +46,7 @@ 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) set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}) @@ -51,6 +54,125 @@ ##################################################################### +## List of source files +##################################################################### + +set(ORTHANC_CORE_SOURCES + Core/Cache/MemoryCache.cpp + Core/ChunkedBuffer.cpp + Core/Compression/BufferCompressor.cpp + Core/Compression/ZlibCompressor.cpp + Core/Compression/ZipWriter.cpp + Core/Compression/HierarchicalZipWriter.cpp + Core/OrthancException.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/Enumerations.cpp + Core/FileStorage/FileStorage.cpp + Core/FileStorage/StorageAccessor.cpp + Core/FileStorage/CompressedFileStorageAccessor.cpp + Core/FileStorage/FileStorageAccessor.cpp + Core/HttpClient.cpp + Core/HttpServer/EmbeddedResourceHttpHandler.cpp + Core/HttpServer/FilesystemHttpHandler.cpp + Core/HttpServer/HttpHandler.cpp + Core/HttpServer/HttpOutput.cpp + Core/HttpServer/MongooseServer.cpp + Core/HttpServer/HttpFileSender.cpp + Core/HttpServer/FilesystemHttpSender.cpp + Core/RestApi/RestApiPath.cpp + Core/RestApi/RestApiOutput.cpp + Core/RestApi/RestApi.cpp + Core/MultiThreading/ArrayFilledByThreads.cpp + Core/MultiThreading/BagOfRunnablesBySteps.cpp + Core/MultiThreading/Mutex.cpp + Core/MultiThreading/ReaderWriterLock.cpp + Core/MultiThreading/SharedMessageQueue.cpp + Core/MultiThreading/ThreadedCommandProcessor.cpp + Core/ImageFormats/ImageAccessor.cpp + Core/ImageFormats/ImageBuffer.cpp + Core/ImageFormats/ImageProcessing.cpp + Core/ImageFormats/PngReader.cpp + Core/ImageFormats/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 + + OrthancCppClient/OrthancConnection.cpp + OrthancCppClient/Study.cpp + OrthancCppClient/Series.cpp + OrthancCppClient/Instance.cpp + OrthancCppClient/Patient.cpp + ) + + +set(ORTHANC_SERVER_SOURCES + OrthancServer/DicomProtocol/DicomFindAnswers.cpp + OrthancServer/DicomProtocol/DicomServer.cpp + OrthancServer/DicomProtocol/DicomUserConnection.cpp + OrthancServer/DicomProtocol/RemoteModalityParameters.cpp + OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp + OrthancServer/DicomModification.cpp + OrthancServer/FromDcmtkBridge.cpp + OrthancServer/ParsedDicomFile.cpp + OrthancServer/Internals/CommandDispatcher.cpp + OrthancServer/Internals/FindScp.cpp + OrthancServer/Internals/MoveScp.cpp + OrthancServer/Internals/StoreScp.cpp + OrthancServer/Internals/DicomImageDecoder.cpp + OrthancServer/OrthancInitialization.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/ServerIndex.cpp + OrthancServer/ToDcmtkBridge.cpp + OrthancServer/DatabaseWrapper.cpp + OrthancServer/ServerContext.cpp + OrthancServer/ServerEnumerations.cpp + OrthancServer/ServerToolbox.cpp + OrthancServer/OrthancFindRequestHandler.cpp + OrthancServer/OrthancMoveRequestHandler.cpp + ) + + +set(ORTHANC_UNIT_TESTS_SOURCES + UnitTestsSources/DicomMap.cpp + UnitTestsSources/FileStorage.cpp + UnitTestsSources/FromDcmtk.cpp + UnitTestsSources/MemoryCache.cpp + UnitTestsSources/Png.cpp + UnitTestsSources/RestApi.cpp + UnitTestsSources/SQLite.cpp + UnitTestsSources/SQLiteChromium.cpp + UnitTestsSources/ServerIndexTests.cpp + UnitTestsSources/Versions.cpp + UnitTestsSources/Zip.cpp + UnitTestsSources/Lua.cpp + UnitTestsSources/MultiThreading.cpp + UnitTestsSources/UnitTestsMain.cpp + UnitTestsSources/ImageProcessingTests.cpp + UnitTestsSources/JpegLossless.cpp + ) + + + +##################################################################### ## Inclusion of third-party dependencies ##################################################################### @@ -86,6 +208,20 @@ 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() + + ##################################################################### ## Autogeneration of files @@ -123,6 +259,21 @@ ## 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}" ) @@ -137,57 +288,7 @@ ${AUTOGENERATED_SOURCES} ${THIRD_PARTY_SOURCES} ${CURL_SOURCES} - - Core/Cache/MemoryCache.cpp - Core/ChunkedBuffer.cpp - Core/Compression/BufferCompressor.cpp - Core/Compression/ZlibCompressor.cpp - Core/Compression/ZipWriter.cpp - Core/Compression/HierarchicalZipWriter.cpp - Core/OrthancException.cpp - Core/DicomFormat/DicomArray.cpp - Core/DicomFormat/DicomMap.cpp - Core/DicomFormat/DicomTag.cpp - Core/DicomFormat/DicomIntegerPixelAccessor.cpp - Core/DicomFormat/DicomInstanceHasher.cpp - Core/Enumerations.cpp - Core/FileStorage/FileStorage.cpp - Core/FileStorage/StorageAccessor.cpp - Core/FileStorage/CompressedFileStorageAccessor.cpp - Core/FileStorage/FileStorageAccessor.cpp - Core/HttpClient.cpp - Core/HttpServer/EmbeddedResourceHttpHandler.cpp - Core/HttpServer/FilesystemHttpHandler.cpp - Core/HttpServer/HttpHandler.cpp - Core/HttpServer/HttpOutput.cpp - Core/HttpServer/MongooseServer.cpp - Core/HttpServer/HttpFileSender.cpp - Core/HttpServer/FilesystemHttpSender.cpp - Core/RestApi/RestApiPath.cpp - Core/RestApi/RestApiOutput.cpp - Core/RestApi/RestApi.cpp - Core/MultiThreading/BagOfRunnablesBySteps.cpp - Core/MultiThreading/SharedMessageQueue.cpp - Core/MultiThreading/ThreadedCommandProcessor.cpp - Core/MultiThreading/ArrayFilledByThreads.cpp - Core/FileFormats/PngReader.cpp - Core/FileFormats/PngWriter.cpp - Core/SQLite/Connection.cpp - 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 - - OrthancCppClient/OrthancConnection.cpp - OrthancCppClient/Study.cpp - OrthancCppClient/Series.cpp - OrthancCppClient/Instance.cpp - OrthancCppClient/Patient.cpp + ${ORTHANC_CORE_SOURCES} ) @@ -198,30 +299,7 @@ add_library(ServerLibrary STATIC ${DCMTK_SOURCES} - OrthancServer/DicomProtocol/DicomFindAnswers.cpp - OrthancServer/DicomProtocol/DicomServer.cpp - OrthancServer/DicomProtocol/DicomUserConnection.cpp - OrthancServer/FromDcmtkBridge.cpp - OrthancServer/Internals/CommandDispatcher.cpp - OrthancServer/Internals/FindScp.cpp - OrthancServer/Internals/MoveScp.cpp - OrthancServer/Internals/StoreScp.cpp - OrthancServer/OrthancInitialization.cpp - OrthancServer/OrthancRestApi/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/ServerIndex.cpp - OrthancServer/ToDcmtkBridge.cpp - OrthancServer/DatabaseWrapper.cpp - OrthancServer/ServerContext.cpp - OrthancServer/ServerEnumerations.cpp - OrthancServer/ServerToolbox.cpp - OrthancServer/OrthancFindRequestHandler.cpp - OrthancServer/OrthancMoveRequestHandler.cpp + ${ORTHANC_SERVER_SOURCES} ) # Ensure autogenerated code is built before building ServerLibrary @@ -231,7 +309,7 @@ OrthancServer/main.cpp ) -target_link_libraries(Orthanc ServerLibrary CoreLibrary) +target_link_libraries(Orthanc ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(Orthanc OpenSSL) @@ -258,21 +336,9 @@ include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) add_executable(UnitTests ${GTEST_SOURCES} - UnitTestsSources/DicomMap.cpp - UnitTestsSources/FileStorage.cpp - UnitTestsSources/MemoryCache.cpp - UnitTestsSources/Png.cpp - UnitTestsSources/RestApi.cpp - UnitTestsSources/SQLite.cpp - UnitTestsSources/SQLiteChromium.cpp - UnitTestsSources/ServerIndexTests.cpp - UnitTestsSources/Versions.cpp - UnitTestsSources/Zip.cpp - UnitTestsSources/Lua.cpp - UnitTestsSources/MultiThreading.cpp - UnitTestsSources/UnitTestsMain.cpp + ${ORTHANC_UNIT_TESTS_SOURCES} ) -target_link_libraries(UnitTests ServerLibrary CoreLibrary) +target_link_libraries(UnitTests ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(UnitTests OpenSSL) @@ -318,28 +384,18 @@ endif() add_library(OrthancClient SHARED - ${ORTHANC_ROOT}/Core/OrthancException.cpp - ${ORTHANC_ROOT}/Core/Enumerations.cpp - ${ORTHANC_ROOT}/Core/Toolbox.cpp - ${ORTHANC_ROOT}/Core/HttpClient.cpp - ${ORTHANC_ROOT}/Core/MultiThreading/ArrayFilledByThreads.cpp - ${ORTHANC_ROOT}/Core/MultiThreading/ThreadedCommandProcessor.cpp - ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp - ${ORTHANC_ROOT}/Core/FileFormats/PngReader.cpp - ${ORTHANC_ROOT}/OrthancCppClient/OrthancConnection.cpp - ${ORTHANC_ROOT}/OrthancCppClient/Series.cpp - ${ORTHANC_ROOT}/OrthancCppClient/Study.cpp - ${ORTHANC_ROOT}/OrthancCppClient/Instance.cpp - ${ORTHANC_ROOT}/OrthancCppClient/Patient.cpp + ${ORTHANC_ROOT}/OrthancCppClient/OrthancCppClient.cpp ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/SharedLibrary.cpp ${ORTHANC_ROOT}/Resources/md5/md5.c ${ORTHANC_ROOT}/Resources/base64/base64.cpp ${ORTHANC_CPP_CLIENT_AUX} ${THIRD_PARTY_SOURCES} ${CURL_SOURCES} + ${GOOGLE_LOG_SOURCES} ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") set_target_properties(OrthancClient PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--as-needed -Wl,--version-script=${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw/VersionScript.map" ) @@ -354,6 +410,10 @@ ) endif() + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + # TODO + target_link_libraries(OrthancClient pthread) + else() message(FATAL_ERROR "Support your platform here") endif()
--- a/Core/Cache/MemoryCache.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Cache/MemoryCache.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "MemoryCache.h" #include <stdlib.h> // This fixes a problem in glog for recent
--- a/Core/ChunkedBuffer.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/ChunkedBuffer.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeaders.h" #include "ChunkedBuffer.h" #include <cassert> @@ -64,6 +65,15 @@ } + void ChunkedBuffer::AddChunk(const std::string& chunk) + { + if (chunk.size() > 0) + { + AddChunk(&chunk[0], chunk.size()); + } + } + + void ChunkedBuffer::Flatten(std::string& result) { result.resize(numBytes_);
--- a/Core/ChunkedBuffer.h Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/ChunkedBuffer.h Wed Jun 25 12:09:38 2014 +0200 @@ -64,6 +64,8 @@ void AddChunk(const char* chunkData, size_t chunkSize); + void AddChunk(const std::string& chunk); + void Flatten(std::string& result); }; }
--- a/Core/Compression/BufferCompressor.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Compression/BufferCompressor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "BufferCompressor.h" namespace Orthanc
--- a/Core/Compression/HierarchicalZipWriter.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Compression/HierarchicalZipWriter.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "HierarchicalZipWriter.h" #include "../Toolbox.h"
--- a/Core/Compression/ZipWriter.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Compression/ZipWriter.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -29,6 +29,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + +#include "../PrecompiledHeaders.h" + #ifndef NOMINMAX #define NOMINMAX #endif
--- a/Core/Compression/ZlibCompressor.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Compression/ZlibCompressor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "ZlibCompressor.h" #include <stdio.h>
--- a/Core/DicomFormat/DicomArray.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/DicomFormat/DicomArray.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "DicomArray.h" #include <stdio.h>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomFormat/DicomImageInformation.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,188 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "DicomImageInformation.h" + +#include "../OrthancException.h" +#include <boost/lexical_cast.hpp> +#include <limits> +#include <cassert> +#include <stdio.h> + +namespace Orthanc +{ + DicomImageInformation::DicomImageInformation(const DicomMap& values) + { + unsigned int pixelRepresentation; + unsigned int planarConfiguration = 0; + + try + { + width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).AsString()); + height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).AsString()); + bitsAllocated_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).AsString()); + + try + { + samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).AsString()); + } + catch (OrthancException&) + { + samplesPerPixel_ = 1; // Assume 1 color channel + } + + try + { + bitsStored_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_STORED).AsString()); + } + catch (OrthancException&) + { + bitsStored_ = bitsAllocated_; + } + + try + { + highBit_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_HIGH_BIT).AsString()); + } + catch (OrthancException&) + { + highBit_ = bitsStored_ - 1; + } + + try + { + pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).AsString()); + } + catch (OrthancException&) + { + pixelRepresentation = 0; // Assume unsigned pixels + } + + 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/ + try + { + planarConfiguration = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).AsString()); + } + catch (OrthancException&) + { + planarConfiguration = 0; // Assume interleaved color channels + } + } + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_NotImplemented); + } + catch (OrthancException&) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + try + { + numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).AsString()); + } + catch (OrthancException) + { + // If the tag "NumberOfFrames" is absent, assume there is a single frame + numberOfFrames_ = 1; + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + if ((bitsAllocated_ != 8 && bitsAllocated_ != 16 && + bitsAllocated_ != 24 && bitsAllocated_ != 32) || + numberOfFrames_ == 0 || + (planarConfiguration != 0 && planarConfiguration != 1)) + { + 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); + } + + bytesPerValue_ = bitsAllocated_ / 8; + + isPlanar_ = (planarConfiguration != 0 ? true : false); + isSigned_ = (pixelRepresentation != 0 ? true : false); + } + + + bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format) const + { + if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_Grayscale8; + return true; + } + + if (GetBitsStored() == 8 && GetChannelCount() == 3 && !IsSigned()) + { + format = PixelFormat_RGB24; + return true; + } + + if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && !IsSigned()) + { + format = PixelFormat_Grayscale16; + return true; + } + + if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && IsSigned()) + { + format = PixelFormat_SignedGrayscale16; + return true; + } + + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomFormat/DicomImageInformation.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,117 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "DicomMap.h" + +#include <stdint.h> + +namespace Orthanc +{ + class DicomImageInformation + { + private: + unsigned int width_; + unsigned int height_; + unsigned int samplesPerPixel_; + unsigned int numberOfFrames_; + + bool isPlanar_; + bool isSigned_; + size_t bytesPerValue_; + + unsigned int bitsAllocated_; + unsigned int bitsStored_; + unsigned int highBit_; + + public: + DicomImageInformation(const DicomMap& values); + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetNumberOfFrames() const + { + return numberOfFrames_; + } + + unsigned int GetChannelCount() const + { + return samplesPerPixel_; + } + + unsigned int GetBitsStored() const + { + return bitsStored_; + } + + size_t GetBytesPerValue() const + { + return bytesPerValue_; + } + + bool IsSigned() const + { + return isSigned_; + } + + unsigned int GetBitsAllocated() const + { + return bitsAllocated_; + } + + unsigned int GetHighBit() const + { + return highBit_; + } + + bool IsPlanar() const + { + return isPlanar_; + } + + unsigned int GetShift() const + { + return highBit_ + 1 - bitsStored_; + } + + bool ExtractPixelFormat(PixelFormat& format) const; + }; +}
--- a/Core/DicomFormat/DicomInstanceHasher.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -29,6 +29,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + +#include "../PrecompiledHeaders.h" #include "DicomInstanceHasher.h" #include "../OrthancException.h"
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,8 @@ **/ +#include "../PrecompiledHeaders.h" + #ifndef NOMINMAX #define NOMINMAX #endif @@ -44,114 +46,45 @@ namespace Orthanc { - static const DicomTag COLUMNS(0x0028, 0x0011); - static const DicomTag ROWS(0x0028, 0x0010); - static const DicomTag SAMPLES_PER_PIXEL(0x0028, 0x0002); - static const DicomTag BITS_ALLOCATED(0x0028, 0x0100); - static const DicomTag BITS_STORED(0x0028, 0x0101); - static const DicomTag HIGH_BIT(0x0028, 0x0102); - static const DicomTag PIXEL_REPRESENTATION(0x0028, 0x0103); - static const DicomTag PLANAR_CONFIGURATION(0x0028, 0x0006); - DicomIntegerPixelAccessor::DicomIntegerPixelAccessor(const DicomMap& values, const void* pixelData, size_t size) : + information_(values), pixelData_(pixelData), size_(size) { - unsigned int bitsAllocated; - unsigned int bitsStored; - unsigned int highBit; - unsigned int pixelRepresentation; - planarConfiguration_ = 0; - - try - { - width_ = boost::lexical_cast<unsigned int>(values.GetValue(COLUMNS).AsString()); - height_ = boost::lexical_cast<unsigned int>(values.GetValue(ROWS).AsString()); - samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(SAMPLES_PER_PIXEL).AsString()); - bitsAllocated = boost::lexical_cast<unsigned int>(values.GetValue(BITS_ALLOCATED).AsString()); - bitsStored = boost::lexical_cast<unsigned int>(values.GetValue(BITS_STORED).AsString()); - highBit = boost::lexical_cast<unsigned int>(values.GetValue(HIGH_BIT).AsString()); - pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(PIXEL_REPRESENTATION).AsString()); - - 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/ - planarConfiguration_ = boost::lexical_cast<unsigned int>(values.GetValue(PLANAR_CONFIGURATION).AsString()); - } - } - catch (boost::bad_lexical_cast) - { - throw OrthancException(ErrorCode_NotImplemented); - } - catch (OrthancException) - { - throw OrthancException(ErrorCode_NotImplemented); - } + frame_ = 0; + frameOffset_ = (information_.GetHeight() * information_.GetWidth() * + information_.GetBytesPerValue() * information_.GetChannelCount()); - frame_ = 0; - try - { - numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).AsString()); - } - catch (OrthancException) - { - // If the tag "NumberOfFrames" is absent, assume there is a single frame - numberOfFrames_ = 1; - } - catch (boost::bad_lexical_cast) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if ((bitsAllocated != 8 && bitsAllocated != 16 && - bitsAllocated != 24 && bitsAllocated != 32) || - numberOfFrames_ == 0 || - (planarConfiguration_ != 0 && planarConfiguration_ != 1)) - { - 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); - } - - bytesPerPixel_ = bitsAllocated / 8; - shift_ = highBit + 1 - bitsStored; - frameOffset_ = height_ * width_ * bytesPerPixel_ * samplesPerPixel_; - - if (numberOfFrames_ * frameOffset_ > size) + if (information_.GetNumberOfFrames() * frameOffset_ > size) { throw OrthancException(ErrorCode_BadFileFormat); } - /*printf("%d %d %d %d %d %d %d %d\n", width_, height_, samplesPerPixel_, bitsAllocated, - bitsStored, highBit, pixelRepresentation, numberOfFrames_);*/ - - if (pixelRepresentation) + if (information_.IsSigned()) { // Pixels are signed - mask_ = (1 << (bitsStored - 1)) - 1; - signMask_ = (1 << (bitsStored - 1)); + mask_ = (1 << (information_.GetBitsStored() - 1)) - 1; + signMask_ = (1 << (information_.GetBitsStored() - 1)); } else { // Pixels are unsigned - mask_ = (1 << bitsStored) - 1; + mask_ = (1 << information_.GetBitsStored()) - 1; signMask_ = 0; } - if (planarConfiguration_ == 0) + if (information_.IsPlanar()) + { + /** + * Each color plane shall be sent contiguously. For RGB images, + * this means the order of the pixel values sent is R1, R2, R3, + * ..., G1, G2, G3, ..., B1, B2, B3, etc. + **/ + rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue(); + } + else { /** * The sample values for the first pixel are followed by the @@ -159,16 +92,7 @@ * means the order of the pixel values sent shall be R1, G1, B1, * R2, G2, B2, ..., etc. **/ - rowOffset_ = width_ * bytesPerPixel_ * samplesPerPixel_; - } - else - { - /** - * Each color plane shall be sent contiguously. For RGB images, - * this means the order of the pixel values sent is R1, R2, R3, - * ..., G1, G2, G3, ..., B1, B2, B3, etc. - **/ - rowOffset_ = width_ * bytesPerPixel_; + rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue() * information_.GetChannelCount(); } } @@ -176,7 +100,7 @@ void DicomIntegerPixelAccessor::GetExtremeValues(int32_t& min, int32_t& max) const { - if (height_ == 0 || width_ == 0) + if (information_.GetHeight() == 0 || information_.GetWidth() == 0) { min = max = 0; return; @@ -185,11 +109,11 @@ min = std::numeric_limits<int32_t>::max(); max = std::numeric_limits<int32_t>::min(); - for (unsigned int y = 0; y < height_; y++) + for (unsigned int y = 0; y < information_.GetHeight(); y++) { - for (unsigned int x = 0; x < width_; x++) + for (unsigned int x = 0; x < information_.GetWidth(); x++) { - for (unsigned int c = 0; c < GetChannelCount(); c++) + for (unsigned int c = 0; c < information_.GetChannelCount(); c++) { int32_t v = GetValue(x, y); if (v < min) @@ -206,13 +130,25 @@ unsigned int y, unsigned int channel) const { - assert(x < width_ && y < height_ && channel < samplesPerPixel_); + assert(x < information_.GetWidth() && + y < information_.GetHeight() && + channel < information_.GetChannelCount()); 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/ - if (planarConfiguration_ == 0) + if (information_.IsPlanar()) + { + /** + * Each color plane shall be sent contiguously. For RGB images, + * this means the order of the pixel values sent is R1, R2, R3, + * ..., G1, G2, G3, ..., B1, B2, B3, etc. + **/ + assert(frameOffset_ % information_.GetChannelCount() == 0); + pixel += channel * frameOffset_ / information_.GetChannelCount() + x * information_.GetBytesPerValue(); + } + else { /** * The sample values for the first pixel are followed by the @@ -220,29 +156,19 @@ * means the order of the pixel values sent shall be R1, G1, B1, * R2, G2, B2, ..., etc. **/ - pixel += channel * bytesPerPixel_ + x * samplesPerPixel_ * bytesPerPixel_; - } - else - { - /** - * Each color plane shall be sent contiguously. For RGB images, - * this means the order of the pixel values sent is R1, R2, R3, - * ..., G1, G2, G3, ..., B1, B2, B3, etc. - **/ - assert(frameOffset_ % samplesPerPixel_ == 0); - pixel += channel * frameOffset_ / samplesPerPixel_ + x * bytesPerPixel_; + pixel += channel * information_.GetBytesPerValue() + x * information_.GetChannelCount() * information_.GetBytesPerValue(); } uint32_t v; v = pixel[0]; - if (bytesPerPixel_ >= 2) + if (information_.GetBytesPerValue() >= 2) v = v + (static_cast<uint32_t>(pixel[1]) << 8); - if (bytesPerPixel_ >= 3) + if (information_.GetBytesPerValue() >= 3) v = v + (static_cast<uint32_t>(pixel[2]) << 16); - if (bytesPerPixel_ >= 4) + if (information_.GetBytesPerValue() >= 4) v = v + (static_cast<uint32_t>(pixel[3]) << 24); - v = v >> shift_; + v = v >> information_.GetShift(); if (v & signMask_) { @@ -260,7 +186,7 @@ void DicomIntegerPixelAccessor::SetCurrentFrame(unsigned int frame) { - if (frame >= numberOfFrames_) + if (frame >= information_.GetNumberOfFrames()) { throw OrthancException(ErrorCode_ParameterOutOfRange); }
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Jun 25 12:09:38 2014 +0200 @@ -34,6 +34,8 @@ #include "DicomMap.h" +#include "DicomImageInformation.h" + #include <stdint.h> namespace Orthanc @@ -41,20 +43,14 @@ class DicomIntegerPixelAccessor { private: - unsigned int width_; - unsigned int height_; - unsigned int samplesPerPixel_; - unsigned int numberOfFrames_; - unsigned int planarConfiguration_; + DicomImageInformation information_; + + uint32_t signMask_; + uint32_t mask_; + const void* pixelData_; size_t size_; - - uint8_t shift_; - uint32_t signMask_; - uint32_t mask_; - size_t bytesPerPixel_; unsigned int frame_; - size_t frameOffset_; size_t rowOffset_; @@ -63,19 +59,9 @@ const void* pixelData, size_t size); - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const + const DicomImageInformation GetInformation() const { - return height_; - } - - unsigned int GetNumberOfFrames() const - { - return numberOfFrames_; + return information_; } unsigned int GetCurrentFrame() const @@ -88,11 +74,11 @@ void GetExtremeValues(int32_t& min, int32_t& max) const; - unsigned int GetChannelCount() const + int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const; + + const void* GetPixelData() const { - return samplesPerPixel_; + return pixelData_; } - - int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const; }; }
--- a/Core/DicomFormat/DicomMap.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "DicomMap.h" #include <stdio.h>
--- a/Core/DicomFormat/DicomTag.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "DicomTag.h" #include "../OrthancException.h"
--- a/Core/DicomFormat/DicomTag.h Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/DicomFormat/DicomTag.h Wed Jun 25 12:09:38 2014 +0200 @@ -117,4 +117,15 @@ 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); + + // Tags for images + static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011); + static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010); + static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002); + static const DicomTag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100); + static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101); + static const DicomTag DICOM_TAG_HIGH_BIT(0x0028, 0x0102); + static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); + static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006); + static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); }
--- a/Core/Enumerations.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Enumerations.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeaders.h" #include "Enumerations.h" #include "OrthancException.h" @@ -247,6 +248,19 @@ } + const char* EnumerationToString(ImageFormat format) + { + switch (format) + { + case ImageFormat_Png: + return "Png"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + ResourceType StringToResourceType(const char* type) { std::string s(type); @@ -269,9 +283,44 @@ { return ResourceType_Instance; } - else + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + + ImageFormat StringToImageFormat(const char* format) + { + std::string s(format); + Toolbox::ToUpperCase(s); + + if (s == "PNG") { - throw OrthancException(ErrorCode_ParameterOutOfRange); + return ImageFormat_Png; + } + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + + unsigned int GetBytesPerPixel(PixelFormat format) + { + switch (format) + { + case PixelFormat_Grayscale8: + return 1; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + return 2; + + case PixelFormat_RGB24: + return 3; + + case PixelFormat_RGBA32: + return 4; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); } } }
--- a/Core/Enumerations.h Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Enumerations.h Wed Jun 25 12:09:38 2014 +0200 @@ -68,7 +68,10 @@ ErrorCode_IncompatibleDatabaseVersion, ErrorCode_FullStorage, ErrorCode_CorruptedFile, - ErrorCode_InexistentTag + ErrorCode_InexistentTag, + ErrorCode_ReadOnly, + ErrorCode_IncompatibleImageFormat, + ErrorCode_IncompatibleImageSize }; /** @@ -79,27 +82,34 @@ /** * {summary}{Color image in RGB24 format.} * {description}{This format describes a color image. The pixels are stored in 3 - * consecutive bytes. The memory layout is RGB. + * consecutive bytes. The memory layout is RGB.} **/ - PixelFormat_RGB24, + PixelFormat_RGB24 = 1, + + /** + * {summary}{Color image in RGBA32 format.} + * {description}{This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA.} + **/ + PixelFormat_RGBA32 = 2, /** * {summary}{Graylevel 8bpp image.} * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.} **/ - PixelFormat_Grayscale8, + PixelFormat_Grayscale8 = 3, /** * {summary}{Graylevel, unsigned 16bpp image.} * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.} **/ - PixelFormat_Grayscale16, + PixelFormat_Grayscale16 = 4, /** * {summary}{Graylevel, signed 16bpp image.} * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.} **/ - PixelFormat_SignedGrayscale16 + PixelFormat_SignedGrayscale16 = 5 }; @@ -112,22 +122,22 @@ * {summary}{Rescaled to 8bpp.} * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.} **/ - ImageExtractionMode_Preview, + ImageExtractionMode_Preview = 1, /** * {summary}{Truncation to the [0, 255] range.} **/ - ImageExtractionMode_UInt8, + ImageExtractionMode_UInt8 = 2, /** * {summary}{Truncation to the [0, 65535] range.} **/ - ImageExtractionMode_UInt16, + ImageExtractionMode_UInt16 = 3, /** * {summary}{Truncation to the [-32768, 32767] range.} **/ - ImageExtractionMode_Int16 + ImageExtractionMode_Int16 = 4 }; @@ -212,6 +222,12 @@ }; + enum ImageFormat + { + ImageFormat_Png = 1 + }; + + /** * WARNING: Do not change the explicit values in the enumerations * below this point. This would result in incompatible databases @@ -249,5 +265,11 @@ const char* EnumerationToString(ResourceType type); + const char* EnumerationToString(ImageFormat format); + ResourceType StringToResourceType(const char* type); + + ImageFormat StringToImageFormat(const char* format); + + unsigned int GetBytesPerPixel(PixelFormat format); }
--- a/Core/FileFormats/PngReader.cpp Wed Apr 16 16:34:09 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,305 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PngReader.h" - -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include <png.h> -#include <string.h> // For memcpy() - -namespace Orthanc -{ - namespace - { - struct FileRabi - { - FILE* fp_; - - FileRabi(const char* filename) - { - fp_ = fopen(filename, "rb"); - if (!fp_) - { - throw OrthancException(ErrorCode_InexistentFile); - } - } - - ~FileRabi() - { - if (fp_) - fclose(fp_); - } - }; - } - - - struct PngReader::PngRabi - { - png_structp png_; - png_infop info_; - png_infop endInfo_; - - void Destruct() - { - if (png_) - { - png_destroy_read_struct(&png_, &info_, &endInfo_); - - png_ = NULL; - info_ = NULL; - endInfo_ = NULL; - } - } - - PngRabi() - { - png_ = NULL; - info_ = NULL; - endInfo_ = NULL; - - png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - info_ = png_create_info_struct(png_); - if (!info_) - { - png_destroy_read_struct(&png_, NULL, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - endInfo_ = png_create_info_struct(png_); - if (!info_) - { - png_destroy_read_struct(&png_, &info_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - ~PngRabi() - { - Destruct(); - } - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size); - }; - - - void PngReader::CheckHeader(const void* header) - { - int is_png = !png_sig_cmp((png_bytep) header, 0, 8); - if (!is_png) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - PngReader::PngReader() - { - width_ = 0; - height_ = 0; - pitch_ = 0; - format_ = PixelFormat_Grayscale8; - } - - void PngReader::Read(PngRabi& rabi) - { - png_set_sig_bytes(rabi.png_, 8); - - png_read_info(rabi.png_, rabi.info_); - - png_uint_32 width, height; - int bit_depth, color_type, interlace_type; - int compression_type, filter_method; - // get size and bit-depth of the PNG-image - png_get_IHDR(rabi.png_, rabi.info_, - &width, &height, - &bit_depth, &color_type, &interlace_type, - &compression_type, &filter_method); - - width_ = width; - height_ = height; - - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) - { - format_ = PixelFormat_Grayscale8; - pitch_ = width_; - } - else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) - { - format_ = PixelFormat_Grayscale16; - pitch_ = 2 * width_; - - if (Toolbox::DetectEndianness() == Endianness_Little) - { - png_set_swap(rabi.png_); - } - } - else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) - { - format_ = PixelFormat_Grayscale8; - pitch_ = 3 * width_; - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - - buffer_.resize(height_ * pitch_); - - if (height_ == 0 || width_ == 0) - { - // Empty image, we are done - return; - } - - png_read_update_info(rabi.png_, rabi.info_); - - std::vector<png_bytep> rows(height_); - for (size_t i = 0; i < height_; i++) - { - rows[i] = &buffer_[0] + i * pitch_; - } - - png_read_image(rabi.png_, &rows[0]); - } - - void PngReader::ReadFromFile(const char* filename) - { - FileRabi f(filename); - - char header[8]; - if (fread(header, 1, 8, f.fp_) != 8) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - CheckHeader(header); - - PngRabi rabi; - - if (setjmp(png_jmpbuf(rabi.png_))) - { - rabi.Destruct(); - throw OrthancException(ErrorCode_BadFileFormat); - } - - png_init_io(rabi.png_, f.fp_); - - Read(rabi); - } - - - - namespace - { - struct MemoryBuffer - { - const uint8_t* buffer_; - size_t size_; - size_t pos_; - bool ok_; - }; - } - - - void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, - png_bytep outBytes, - png_size_t byteCountToRead) - { - MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr)); - - if (!from->ok_) - { - return; - } - - if (from->pos_ + byteCountToRead > from->size_) - { - from->ok_ = false; - return; - } - - memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); - - from->pos_ += byteCountToRead; - } - - - void PngReader::ReadFromMemory(const void* buffer, - size_t size) - { - if (size < 8) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - CheckHeader(buffer); - - PngRabi rabi; - - if (setjmp(png_jmpbuf(rabi.png_))) - { - rabi.Destruct(); - throw OrthancException(ErrorCode_BadFileFormat); - } - - MemoryBuffer tmp; - tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header - tmp.size_ = size - 8; - tmp.pos_ = 0; - tmp.ok_ = true; - - png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); - - Read(rabi); - - if (!tmp.ok_) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - void PngReader::ReadFromMemory(const std::string& buffer) - { - if (buffer.size() != 0) - ReadFromMemory(&buffer[0], buffer.size()); - else - ReadFromMemory(NULL, 0); - } -}
--- a/Core/FileFormats/PngReader.h Wed Apr 16 16:34:09 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include <vector> -#include <stdint.h> -#include <boost/shared_ptr.hpp> - -namespace Orthanc -{ - class PngReader - { - private: - struct PngRabi; - - PixelFormat format_; - unsigned int width_; - unsigned int height_; - unsigned int pitch_; - std::vector<uint8_t> buffer_; - - void CheckHeader(const void* header); - - void Read(PngRabi& rabi); - - public: - PngReader(); - - PixelFormat GetFormat() const - { - return format_; - } - - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const - { - return height_; - } - - unsigned int GetPitch() const - { - return pitch_; - } - - const void* GetBuffer() const - { - if (buffer_.size() > 0) - return &buffer_[0]; - else - return NULL; - } - - const void* GetBuffer(unsigned int y) const - { - if (buffer_.size() > 0) - return &buffer_[y * pitch_]; - else - return NULL; - } - - void ReadFromFile(const char* filename); - - void ReadFromFile(const std::string& filename) - { - ReadFromFile(filename.c_str()); - } - - void ReadFromMemory(const void* buffer, - size_t size); - - void ReadFromMemory(const std::string& buffer); - }; -}
--- a/Core/FileFormats/PngWriter.cpp Wed Apr 16 16:34:09 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,263 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PngWriter.h" - -#include <vector> -#include <stdint.h> -#include <png.h> -#include "../OrthancException.h" -#include "../ChunkedBuffer.h" -#include "../Toolbox.h" - - -// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 -// http://zarb.org/~gc/html/libpng.html -/* - void write_row_callback(png_ptr, png_uint_32 row, int pass) - { - }*/ - - - - -/* bool isError_; - -// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 - -static void ErrorHandler(png_structp png, png_const_charp message) -{ -printf("** [%s]\n", message); - -PngWriter* that = (PngWriter*) png_get_error_ptr(png); -that->isError_ = true; -printf("** %d\n", (int)that); - -//((PngWriter*) payload)->isError_ = true; -} - -static void WarningHandler(png_structp png, png_const_charp message) -{ - printf("++ %d\n", (int)message); -}*/ - - -namespace Orthanc -{ - struct PngWriter::PImpl - { - png_structp png_; - png_infop info_; - - // Filled by Prepare() - std::vector<uint8_t*> rows_; - int bitDepth_; - int colorType_; - }; - - - - PngWriter::PngWriter() : pimpl_(new PImpl) - { - pimpl_->png_ = NULL; - pimpl_->info_ = NULL; - - pimpl_->png_ = png_create_write_struct - (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); - if (!pimpl_->png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - pimpl_->info_ = png_create_info_struct(pimpl_->png_); - if (!pimpl_->info_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - PngWriter::~PngWriter() - { - if (pimpl_->info_) - { - png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); - } - - if (pimpl_->png_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - } - } - - - - void PngWriter::Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - pimpl_->rows_.resize(height); - for (unsigned int y = 0; y < height; y++) - { - pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; - } - - switch (format) - { - case PixelFormat_RGB24: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; - break; - - case PixelFormat_Grayscale8: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - pimpl_->bitDepth_ = 16; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void PngWriter::Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format) - { - png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, - pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_write_info(pimpl_->png_, pimpl_->info_); - - if (height > 0) - { - switch (format) - { - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - int transforms = 0; - if (Toolbox::DetectEndianness() == Endianness_Little) - { - transforms = PNG_TRANSFORM_SWAP_ENDIAN; - } - - png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); - png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL); - - break; - } - - default: - png_write_image(pimpl_->png_, &pimpl_->rows_[0]); - } - } - - png_write_end(pimpl_->png_, NULL); - } - - - void PngWriter::WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - Prepare(width, height, pitch, format, buffer); - - FILE* fp = fopen(filename, "wb"); - if (!fp) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - png_init_io(pimpl_->png_, fp); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_CannotWriteFile); - } - - Compress(width, height, pitch, format); - - fclose(fp); - } - - - - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size) - { - ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr)); - buffer->AddChunk(reinterpret_cast<const char*>(data), size); - } - - - - void PngWriter::WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - ChunkedBuffer chunks; - - Prepare(width, height, pitch, format, buffer); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_InternalError); - } - - png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); - - Compress(width, height, pitch, format); - - chunks.Flatten(png); - } -}
--- a/Core/FileFormats/PngWriter.h Wed Apr 16 16:34:09 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include <boost/shared_ptr.hpp> -#include <string> - -namespace Orthanc -{ - class PngWriter - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - void Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format); - - void Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - public: - PngWriter(); - - ~PngWriter(); - - void WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - }; -}
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "CompressedFileStorageAccessor.h" #include "../OrthancException.h"
--- a/Core/FileStorage/FileStorage.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/FileStorage/FileStorage.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "FileStorage.h" // http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system @@ -78,25 +79,10 @@ FileStorage::FileStorage(std::string root) { - namespace fs = boost::filesystem; - //root_ = boost::filesystem::absolute(root).string(); root_ = root; - if (fs::exists(root)) - { - if (!fs::is_directory(root)) - { - throw OrthancException("The file storage root directory is a file"); - } - } - else - { - if (!fs::create_directories(root)) - { - throw OrthancException("Unable to create the file storage root directory"); - } - } + Toolbox::CreateDirectory(root); } std::string FileStorage::CreateFileWithoutCompression(const void* content, size_t size)
--- a/Core/FileStorage/FileStorageAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/FileStorage/FileStorageAccessor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "FileStorageAccessor.h" namespace Orthanc
--- a/Core/FileStorage/StorageAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/FileStorage/StorageAccessor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "StorageAccessor.h" namespace Orthanc
--- a/Core/HttpClient.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpClient.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeaders.h" #include "HttpClient.h" #include "../Core/Toolbox.h"
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "EmbeddedResourceHttpHandler.h" #include "../OrthancException.h"
--- a/Core/HttpServer/FilesystemHttpHandler.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "FilesystemHttpHandler.h" #include "../OrthancException.h"
--- a/Core/HttpServer/FilesystemHttpSender.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -29,6 +29,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ +#include "../PrecompiledHeaders.h" #include "FilesystemHttpSender.h" #include "../Toolbox.h"
--- a/Core/HttpServer/HttpFileSender.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpServer/HttpFileSender.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "HttpFileSender.h" #include <boost/lexical_cast.hpp>
--- a/Core/HttpServer/HttpHandler.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpServer/HttpHandler.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "HttpHandler.h" #include <string.h>
--- a/Core/HttpServer/HttpOutput.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpServer/HttpOutput.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "HttpOutput.h" #include <iostream>
--- a/Core/HttpServer/MongooseServer.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/HttpServer/MongooseServer.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -32,6 +32,7 @@ // http://en.highscore.de/cpp/boost/stringhandling.html +#include "../PrecompiledHeaders.h" #include "MongooseServer.h" #include <algorithm> @@ -474,7 +475,8 @@ } std::string b64 = s.substr(6); - std::string decoded = Toolbox::DecodeBase64(b64); + std::string decoded; + Toolbox::DecodeBase64(decoded, b64); size_t semicolons = decoded.find(':'); if (semicolons == std::string::npos) @@ -849,7 +851,9 @@ Stop(); std::string tag = std::string(username) + ":" + std::string(password); - registeredUsers_.insert(Toolbox::EncodeBase64(tag)); + std::string encoded; + Toolbox::EncodeBase64(encoded, tag); + registeredUsers_.insert(encoded); } void MongooseServer::SetSslEnabled(bool enabled)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageAccessor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,221 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageAccessor.h" + +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" + +#include <stdint.h> +#include <cassert> +#include <glog/logging.h> +#include <boost/lexical_cast.hpp> + +namespace Orthanc +{ + template <typename PixelType> + static void ToMatlabStringInternal(ChunkedBuffer& target, + const ImageAccessor& source) + { + target.AddChunk("double([ "); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); + + std::string s; + if (y > 0) + { + s = "; "; + } + + s.reserve(source.GetWidth() * 8); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++) + { + s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " "; + } + + target.AddChunk(s); + } + + target.AddChunk("])"); + } + + + static void RGB24ToMatlabString(ChunkedBuffer& target, + const ImageAccessor& source) + { + assert(source.GetFormat() == PixelFormat_RGB24); + + target.AddChunk("double(permute(reshape([ "); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + + std::string s; + s.reserve(source.GetWidth() * 3 * 8); + + for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++) + { + s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " "; + } + + target.AddChunk(s); + } + + target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) + + " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))"); + } + + + void* ImageAccessor::GetBuffer() const + { + if (readOnly_) + { + LOG(ERROR) << "Trying to write on a read-only image"; + throw OrthancException(ErrorCode_ReadOnly); + } + + return buffer_; + } + + + const void* ImageAccessor::GetConstRow(unsigned int y) const + { + if (buffer_ != NULL) + { + return reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void* ImageAccessor::GetRow(unsigned int y) const + { + if (readOnly_) + { + LOG(ERROR) << "Trying to write on a read-only image"; + throw OrthancException(ErrorCode_ReadOnly); + } + + if (buffer_ != NULL) + { + return reinterpret_cast<uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void ImageAccessor::AssignEmpty(PixelFormat format) + { + readOnly_ = false; + format_ = format; + width_ = 0; + height_ = 0; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageAccessor::AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer) + { + readOnly_ = true; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = const_cast<void*>(buffer); + + assert(GetBytesPerPixel(format_) * width_ <= pitch_); + } + + + void ImageAccessor::AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer) + { + readOnly_ = false; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = buffer; + + assert(GetBytesPerPixel(format_) * width_ <= pitch_); + } + + + void ImageAccessor::ToMatlabString(std::string& target) const + { + ChunkedBuffer buffer; + + switch (GetFormat()) + { + case PixelFormat_Grayscale8: + ToMatlabStringInternal<uint8_t>(buffer, *this); + break; + + case PixelFormat_Grayscale16: + ToMatlabStringInternal<uint16_t>(buffer, *this); + break; + + case PixelFormat_SignedGrayscale16: + ToMatlabStringInternal<int16_t>(buffer, *this); + break; + + case PixelFormat_RGB24: + RGB24ToMatlabString(buffer, *this); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + buffer.Flatten(target); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageAccessor.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +namespace Orthanc +{ + class ImageAccessor + { + private: + bool readOnly_; + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + public: + ImageAccessor() + { + AssignEmpty(PixelFormat_Grayscale8); + } + + bool IsReadOnly() const + { + return readOnly_; + } + + PixelFormat GetFormat() const + { + return format_; + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetPitch() const + { + return pitch_; + } + + unsigned int GetSize() const + { + return GetHeight() * GetPitch(); + } + + const void* GetConstBuffer() const + { + return buffer_; + } + + void* GetBuffer() const; + + const void* GetConstRow(unsigned int y) const; + + void* GetRow(unsigned int y) const; + + void AssignEmpty(PixelFormat format); + + void AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer); + + void AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer); + + void ToMatlabString(std::string& target) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageBuffer.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,192 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageBuffer.h" + +#include "../OrthancException.h" + +#include <stdio.h> +#include <stdlib.h> + +namespace Orthanc +{ + void ImageBuffer::Allocate() + { + if (changed_) + { + Deallocate(); + + /* + if (forceMinimalPitch_) + { + TODO: Align pitch and memory buffer to optimal size for SIMD. + } + */ + + pitch_ = GetBytesPerPixel() * width_; + size_t size = pitch_ * height_; + + if (size == 0) + { + buffer_ = NULL; + } + else + { + buffer_ = malloc(size); + if (buffer_ == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + changed_ = false; + } + } + + + void ImageBuffer::Deallocate() + { + if (buffer_ != NULL) + { + free(buffer_); + buffer_ = NULL; + changed_ = true; + } + } + + + ImageBuffer::ImageBuffer(unsigned int width, + unsigned int height, + PixelFormat format) + { + Initialize(); + SetWidth(width); + SetHeight(height); + SetFormat(format); + } + + + void ImageBuffer::Initialize() + { + changed_ = false; + forceMinimalPitch_ = true; + format_ = PixelFormat_Grayscale8; + width_ = 0; + height_ = 0; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageBuffer::SetFormat(PixelFormat format) + { + if (format != format_) + { + changed_ = true; + format_ = format; + } + } + + + void ImageBuffer::SetWidth(unsigned int width) + { + if (width != width_) + { + changed_ = true; + width_ = width; + } + } + + + void ImageBuffer::SetHeight(unsigned int height) + { + if (height != height_) + { + changed_ = true; + height_ = height; + } + } + + + ImageAccessor ImageBuffer::GetAccessor() + { + Allocate(); + + ImageAccessor accessor; + accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); + return accessor; + } + + + ImageAccessor ImageBuffer::GetConstAccessor() + { + Allocate(); + + ImageAccessor accessor; + accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_); + return accessor; + } + + + void ImageBuffer::SetMinimalPitchForced(bool force) + { + if (force != forceMinimalPitch_) + { + changed_ = true; + forceMinimalPitch_ = force; + } + } + + + void ImageBuffer::AcquireOwnership(ImageBuffer& other) + { + // Remove the content of the current image + Deallocate(); + + // Force the allocation of the other image (if not already + // allocated) + other.Allocate(); + + // Transfer the content of the other image + changed_ = false; + forceMinimalPitch_ = other.forceMinimalPitch_; + format_ = other.format_; + width_ = other.width_; + height_ = other.height_; + pitch_ = other.pitch_; + buffer_ = other.buffer_; + + // Force the reinitialization of the other image + other.Initialize(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageBuffer.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include <vector> +#include <stdint.h> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ImageBuffer : public boost::noncopyable + { + private: + bool changed_; + + bool forceMinimalPitch_; // Currently unused + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + void Initialize(); + + void Allocate(); + + void Deallocate(); + + public: + ImageBuffer(unsigned int width, + unsigned int height, + PixelFormat format); + + ImageBuffer() + { + Initialize(); + } + + ~ImageBuffer() + { + Deallocate(); + } + + PixelFormat GetFormat() const + { + return format_; + } + + void SetFormat(PixelFormat format); + + unsigned int GetWidth() const + { + return width_; + } + + void SetWidth(unsigned int width); + + unsigned int GetHeight() const + { + return height_; + } + + void SetHeight(unsigned int height); + + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + + ImageAccessor GetAccessor(); + + ImageAccessor GetConstAccessor(); + + bool IsMinimalPitchForced() const + { + return forceMinimalPitch_; + } + + void SetMinimalPitchForced(bool force); + + void AcquireOwnership(ImageBuffer& other); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageProcessing.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,473 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ImageProcessing.h" + +#include "../OrthancException.h" + +#include <boost/math/special_functions/round.hpp> + +#include <cassert> +#include <string.h> +#include <limits> +#include <stdint.h> + +namespace Orthanc +{ + template <typename TargetType, typename SourceType> + static void ConvertInternal(ImageAccessor& target, + const ImageAccessor& source) + { + const TargetType minValue = std::numeric_limits<TargetType>::min(); + const TargetType maxValue = std::numeric_limits<TargetType>::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); + const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++) + { + if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue)) + { + *t = minValue; + } + else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue)) + { + *t = maxValue; + } + else + { + *t = static_cast<TargetType>(*s); + } + } + } + } + + + template <typename PixelType> + static void SetInternal(ImageAccessor& image, + int64_t constant) + { + 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++) + { + *p = static_cast<PixelType>(constant); + } + } + } + + + template <typename PixelType> + static void GetMinMaxValueInternal(PixelType& minValue, + PixelType& maxValue, + const ImageAccessor& source) + { + // Deal with the special case of empty image + if (source.GetWidth() == 0 || + source.GetHeight() == 0) + { + minValue = 0; + maxValue = 0; + return; + } + + minValue = std::numeric_limits<PixelType>::max(); + maxValue = std::numeric_limits<PixelType>::min(); + + 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++) + { + if (*p < minValue) + { + minValue = *p; + } + + if (*p > maxValue) + { + maxValue = *p; + } + } + } + } + + + + template <typename PixelType> + static void AddConstantInternal(ImageAccessor& image, + int64_t constant) + { + if (constant == 0) + { + return; + } + + const int64_t minValue = std::numeric_limits<PixelType>::min(); + const int64_t maxValue = std::numeric_limits<PixelType>::max(); + + 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++) + { + int64_t v = static_cast<int64_t>(*p) + constant; + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(v); + } + } + } + } + + + + template <typename PixelType> + void MultiplyConstantInternal(ImageAccessor& image, + float factor) + { + if (abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon()) + { + return; + } + + const int64_t minValue = std::numeric_limits<PixelType>::min(); + const int64_t maxValue = std::numeric_limits<PixelType>::max(); + + 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++) + { + int64_t v = boost::math::llround(static_cast<float>(*p) * factor); + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(v); + } + } + } + } + + + template <typename PixelType> + 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()); + + 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++) + { + float v = (static_cast<float>(*p) + offset) * scaling; + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(boost::math::iround(v)); + } + } + } + } + + + void ImageProcessing::Copy(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (target.GetFormat() != source.GetFormat()) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth(); + + assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + memcpy(target.GetRow(y), source.GetConstRow(y), lineSize); + } + } + + + void ImageProcessing::Convert(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (source.GetFormat() == target.GetFormat()) + { + Copy(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal<uint16_t, uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal<int16_t, uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal<uint8_t, uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal<int16_t, uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal<uint8_t, int16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal<uint16_t, int16_t>(target, source); + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + + void ImageProcessing::Set(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + SetInternal<uint8_t>(image, value); + return; + + case PixelFormat_Grayscale16: + SetInternal<uint16_t>(image, value); + return; + + case PixelFormat_SignedGrayscale16: + SetInternal<int16_t>(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftRight(ImageAccessor& image, + unsigned int shift) + { + if (image.GetWidth() == 0 || + image.GetHeight() == 0 || + shift == 0) + { + // Nothing to do + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + void ImageProcessing::GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + uint8_t a, b; + GetMinMaxValueInternal<uint8_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_Grayscale16: + { + uint16_t a, b; + GetMinMaxValueInternal<uint16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_SignedGrayscale16: + { + int16_t a, b; + GetMinMaxValueInternal<int16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + + void ImageProcessing::AddConstant(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + AddConstantInternal<uint8_t>(image, value); + return; + + case PixelFormat_Grayscale16: + AddConstantInternal<uint16_t>(image, value); + return; + + case PixelFormat_SignedGrayscale16: + AddConstantInternal<int16_t>(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::MultiplyConstant(ImageAccessor& image, + float factor) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + MultiplyConstantInternal<uint8_t>(image, factor); + return; + + case PixelFormat_Grayscale16: + MultiplyConstantInternal<uint16_t>(image, factor); + return; + + case PixelFormat_SignedGrayscale16: + MultiplyConstantInternal<int16_t>(image, factor); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftScale(ImageAccessor& image, + float offset, + float scaling) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + ShiftScaleInternal<uint8_t>(image, offset, scaling); + return; + + case PixelFormat_Grayscale16: + ShiftScaleInternal<uint16_t>(image, offset, scaling); + return; + + case PixelFormat_SignedGrayscale16: + ShiftScaleInternal<int16_t>(image, offset, scaling); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/ImageProcessing.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,70 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include <stdint.h> + +namespace Orthanc +{ + class ImageProcessing + { + public: + static void Copy(ImageAccessor& target, + const ImageAccessor& source); + + static void Convert(ImageAccessor& target, + const ImageAccessor& source); + + static void Set(ImageAccessor& image, + int64_t value); + + static void ShiftRight(ImageAccessor& target, + unsigned int shift); + + static void GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image); + + static void AddConstant(ImageAccessor& image, + int64_t value); + + static void MultiplyConstant(ImageAccessor& image, + float factor); + + static void ShiftScale(ImageAccessor& image, + float offset, + float scaling); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/PngReader.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,313 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "PngReader.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include <png.h> +#include <string.h> // For memcpy() + +namespace Orthanc +{ + namespace + { + struct FileRabi + { + FILE* fp_; + + FileRabi(const char* filename) + { + fp_ = fopen(filename, "rb"); + if (!fp_) + { + throw OrthancException(ErrorCode_InexistentFile); + } + } + + ~FileRabi() + { + if (fp_) + fclose(fp_); + } + }; + } + + + struct PngReader::PngRabi + { + png_structp png_; + png_infop info_; + png_infop endInfo_; + + void Destruct() + { + if (png_) + { + png_destroy_read_struct(&png_, &info_, &endInfo_); + + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + } + } + + PngRabi() + { + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + info_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, NULL, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + endInfo_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, &info_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + ~PngRabi() + { + Destruct(); + } + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size); + }; + + + void PngReader::CheckHeader(const void* header) + { + int is_png = !png_sig_cmp((png_bytep) header, 0, 8); + if (!is_png) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + PngReader::PngReader() + { + } + + void PngReader::Read(PngRabi& rabi) + { + png_set_sig_bytes(rabi.png_, 8); + + png_read_info(rabi.png_, rabi.info_); + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + int compression_type, filter_method; + // get size and bit-depth of the PNG-image + png_get_IHDR(rabi.png_, rabi.info_, + &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_method); + + PixelFormat format; + unsigned int pitch; + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) + { + format = PixelFormat_Grayscale8; + pitch = width; + } + else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) + { + format = PixelFormat_Grayscale16; + pitch = 2 * width; + + if (Toolbox::DetectEndianness() == Endianness_Little) + { + png_set_swap(rabi.png_); + } + } + else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) + { + format = PixelFormat_RGB24; + pitch = 3 * width; + } + else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8) + { + format = PixelFormat_RGBA32; + pitch = 4 * width; + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + + data_.resize(height * pitch); + + if (height == 0 || width == 0) + { + // Empty image, we are done + AssignEmpty(format); + return; + } + + png_read_update_info(rabi.png_, rabi.info_); + + std::vector<png_bytep> rows(height); + for (size_t i = 0; i < height; i++) + { + rows[i] = &data_[0] + i * pitch; + } + + png_read_image(rabi.png_, &rows[0]); + + AssignReadOnly(format, width, height, pitch, &data_[0]); + } + + void PngReader::ReadFromFile(const char* filename) + { + FileRabi f(filename); + + char header[8]; + if (fread(header, 1, 8, f.fp_) != 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(header); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + png_init_io(rabi.png_, f.fp_); + + Read(rabi); + } + + + namespace + { + struct MemoryBuffer + { + const uint8_t* buffer_; + size_t size_; + size_t pos_; + bool ok_; + }; + } + + + void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, + png_bytep outBytes, + png_size_t byteCountToRead) + { + MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr)); + + if (!from->ok_) + { + return; + } + + if (from->pos_ + byteCountToRead > from->size_) + { + from->ok_ = false; + return; + } + + memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); + + from->pos_ += byteCountToRead; + } + + + void PngReader::ReadFromMemory(const void* buffer, + size_t size) + { + if (size < 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(buffer); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + MemoryBuffer tmp; + tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header + tmp.size_ = size - 8; + tmp.pos_ = 0; + tmp.ok_ = true; + + png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); + + Read(rabi); + + if (!tmp.ok_) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + void PngReader::ReadFromMemory(const std::string& buffer) + { + if (buffer.size() != 0) + { + ReadFromMemory(&buffer[0], buffer.size()); + } + else + { + ReadFromMemory(NULL, 0); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/PngReader.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,71 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include "../Enumerations.h" + +#include <vector> +#include <stdint.h> +#include <boost/shared_ptr.hpp> + +namespace Orthanc +{ + class PngReader : public ImageAccessor + { + private: + struct PngRabi; + + std::vector<uint8_t> data_; + + void CheckHeader(const void* header); + + void Read(PngRabi& rabi); + + public: + PngReader(); + + void ReadFromFile(const char* filename); + + void ReadFromFile(const std::string& filename) + { + ReadFromFile(filename.c_str()); + } + + void ReadFromMemory(const void* buffer, + size_t size); + + void ReadFromMemory(const std::string& buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/PngWriter.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,269 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "PngWriter.h" + +#include <vector> +#include <stdint.h> +#include <png.h> +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" +#include "../Toolbox.h" + + +// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 +// http://zarb.org/~gc/html/libpng.html +/* + void write_row_callback(png_ptr, png_uint_32 row, int pass) + { + }*/ + + + + +/* bool isError_; + +// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 + +static void ErrorHandler(png_structp png, png_const_charp message) +{ +printf("** [%s]\n", message); + +PngWriter* that = (PngWriter*) png_get_error_ptr(png); +that->isError_ = true; +printf("** %d\n", (int)that); + +//((PngWriter*) payload)->isError_ = true; +} + +static void WarningHandler(png_structp png, png_const_charp message) +{ + printf("++ %d\n", (int)message); +}*/ + + +namespace Orthanc +{ + struct PngWriter::PImpl + { + png_structp png_; + png_infop info_; + + // Filled by Prepare() + std::vector<uint8_t*> rows_; + int bitDepth_; + int colorType_; + }; + + + + PngWriter::PngWriter() : pimpl_(new PImpl) + { + pimpl_->png_ = NULL; + pimpl_->info_ = NULL; + + pimpl_->png_ = png_create_write_struct + (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); + if (!pimpl_->png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + pimpl_->info_ = png_create_info_struct(pimpl_->png_); + if (!pimpl_->info_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + PngWriter::~PngWriter() + { + if (pimpl_->info_) + { + png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); + } + + if (pimpl_->png_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + } + } + + + + void PngWriter::Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + pimpl_->rows_.resize(height); + for (unsigned int y = 0; y < height; y++) + { + pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; + } + + switch (format) + { + case PixelFormat_RGB24: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; + break; + + case PixelFormat_RGBA32: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA; + break; + + case PixelFormat_Grayscale8: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + pimpl_->bitDepth_ = 16; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void PngWriter::Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format) + { + png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, + pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(pimpl_->png_, pimpl_->info_); + + if (height > 0) + { + switch (format) + { + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + int transforms = 0; + if (Toolbox::DetectEndianness() == Endianness_Little) + { + transforms = PNG_TRANSFORM_SWAP_ENDIAN; + } + + png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); + png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL); + + break; + } + + default: + png_write_image(pimpl_->png_, &pimpl_->rows_[0]); + } + } + + png_write_end(pimpl_->png_, NULL); + } + + + void PngWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + Prepare(width, height, pitch, format, buffer); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + png_init_io(pimpl_->png_, fp); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_CannotWriteFile); + } + + Compress(width, height, pitch, format); + + fclose(fp); + } + + + + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size) + { + ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr)); + buffer->AddChunk(reinterpret_cast<const char*>(data), size); + } + + + + void PngWriter::WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + ChunkedBuffer chunks; + + Prepare(width, height, pitch, format, buffer); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_InternalError); + } + + png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); + + Compress(width, height, pitch, format); + + chunks.Flatten(png); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/ImageFormats/PngWriter.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,92 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ImageAccessor.h" + +#include <boost/shared_ptr.hpp> +#include <string> + +namespace Orthanc +{ + class PngWriter + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + void Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format); + + void Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + public: + PngWriter(); + + ~PngWriter(); + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + 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()); + } + }; +}
--- a/Core/Lua/LuaContext.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Lua/LuaContext.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "LuaContext.h" #include <glog/logging.h>
--- a/Core/Lua/LuaFunctionCall.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Lua/LuaFunctionCall.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "LuaFunctionCall.h"
--- a/Core/MultiThreading/ArrayFilledByThreads.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/MultiThreading/ArrayFilledByThreads.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" #include "ArrayFilledByThreads.h" #include "../MultiThreading/ThreadedCommandProcessor.h"
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "BagOfRunnablesBySteps.h" #include <stack>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ILockable.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <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/Core/MultiThreading/Locker.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "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/Core/MultiThreading/Mutex.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,121 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "Mutex.h" + +#include "../OrthancException.h" + +#if defined(_WIN32) +#include <windows.h> +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) +#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__) + + 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/Core/MultiThreading/Mutex.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,56 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "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/Core/MultiThreading/ReaderWriterLock.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,126 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../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_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/MultiThreading/ReaderWriterLock.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "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/SharedMessageQueue.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/MultiThreading/SharedMessageQueue.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "SharedMessageQueue.h" namespace Orthanc
--- a/Core/MultiThreading/ThreadedCommandProcessor.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "ThreadedCommandProcessor.h" #include "../OrthancException.h"
--- a/Core/OrthancException.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/OrthancException.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeaders.h" #include "OrthancException.h" namespace Orthanc @@ -111,6 +112,15 @@ case ErrorCode_InexistentTag: return "Inexistent tag"; + case ErrorCode_ReadOnly: + return "Cannot modify a read-only data structure"; + + case ErrorCode_IncompatibleImageSize: + return "Incompatible size of the images"; + + case ErrorCode_IncompatibleImageFormat: + return "Incompatible format of the images"; + case ErrorCode_Custom: default: return "???";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/PrecompiledHeaders.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,33 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeaders.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/PrecompiledHeaders.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_USE_PRECOMPILED_HEADERS == 1 + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/locale.hpp> +#include <boost/regex.hpp> +#include <boost/thread.hpp> +#include <boost/thread/shared_mutex.hpp> + +#include <glog/logging.h> +#include <json/value.h> + +#include "Enumerations.h" +#include "OrthancException.h" +#include "Toolbox.h" +#include "Uuid.h" + +#endif
--- a/Core/RestApi/RestApi.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/RestApi/RestApi.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "RestApi.h" #include <stdlib.h> // To define "_exit()" under Windows
--- a/Core/RestApi/RestApiOutput.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/RestApi/RestApiOutput.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "RestApiOutput.h" #include <boost/lexical_cast.hpp>
--- a/Core/RestApi/RestApiPath.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/RestApi/RestApiPath.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "RestApiPath.h" #include <cassert>
--- a/Core/SQLite/Connection.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/SQLite/Connection.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -34,6 +34,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "Connection.h" #include <memory>
--- a/Core/SQLite/FunctionContext.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/SQLite/FunctionContext.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -31,6 +31,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "FunctionContext.h" #include <sqlite3.h>
--- a/Core/SQLite/Statement.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/SQLite/Statement.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -34,6 +34,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "Statement.h" #include "Connection.h"
--- a/Core/SQLite/StatementId.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/SQLite/StatementId.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -34,6 +34,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "StatementId.h" #include <string.h>
--- a/Core/SQLite/StatementReference.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/SQLite/StatementReference.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -34,6 +34,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "StatementReference.h" #include "../OrthancException.h"
--- a/Core/SQLite/Transaction.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/SQLite/Transaction.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -34,6 +34,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "Transaction.h" namespace Orthanc
--- a/Core/Toolbox.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Toolbox.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeaders.h" #include "Toolbox.h" #include "OrthancException.h" @@ -53,17 +54,17 @@ #include <limits.h> /* PATH_MAX */ #endif -#if defined(__linux) +#if defined(__linux) || defined(__FreeBSD_kernel__) #include <limits.h> /* PATH_MAX */ #include <signal.h> #include <unistd.h> #endif -#if BOOST_HAS_LOCALE == 1 +#if BOOST_HAS_LOCALE != 1 +#error Since version 0.7.6, Orthanc entirely relies on boost::locale +#endif + #include <boost/locale.hpp> -#else -#include <iconv.h> -#endif #include "../Resources/md5/md5.h" #include "../Resources/base64/base64.h" @@ -73,68 +74,11 @@ // Patch for the missing "_strtoll" symbol when compiling with Visual Studio extern "C" { -int64_t _strtoi64(const char *nptr, char **endptr, int base); -int64_t strtoll(const char *nptr, char **endptr, int base) -{ - return _strtoi64(nptr, endptr, base); -} -} -#endif - - -#if BOOST_HAS_LOCALE == 0 -namespace -{ - class IconvRabi + int64_t _strtoi64(const char *nptr, char **endptr, int base); + int64_t strtoll(const char *nptr, char **endptr, int base) { - private: - iconv_t context_; - - public: - IconvRabi(const char* tocode, const char* fromcode) - { - context_ = iconv_open(tocode, fromcode); - if (!context_) - { - throw Orthanc::OrthancException("Unknown code page"); - } - } - - ~IconvRabi() - { - iconv_close(context_); - } - - std::string Convert(const std::string& source) - { - if (source.size() == 0) - { - return ""; - } - - std::string result; - char* sourcePos = const_cast<char*>(&source[0]); - size_t sourceLeft = source.size(); - - std::vector<char> storage(source.size() + 10); - - while (sourceLeft > 0) - { - char* tmp = &storage[0]; - size_t outputLeft = storage.size(); - size_t err = iconv(context_, &sourcePos, &sourceLeft, &tmp, &outputLeft); - if (err < 0) - { - throw Orthanc::OrthancException("Bad character in sequence"); - } - - size_t count = storage.size() - outputLeft; - result += std::string(&storage[0], count); - } - - return result; - } - }; + return _strtoi64(nptr, endptr, base); + } } #endif @@ -161,7 +105,7 @@ { #if defined(_WIN32) ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000))); -#elif defined(__linux) +#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) usleep(microSeconds); #else #error Support your platform here @@ -491,14 +435,16 @@ } - std::string Toolbox::EncodeBase64(const std::string& data) + void Toolbox::EncodeBase64(std::string& result, + const std::string& data) { - return base64_encode(data); + result = base64_encode(data); } - std::string Toolbox::DecodeBase64(const std::string& data) + void Toolbox::DecodeBase64(std::string& result, + const std::string& data) { - return base64_decode(data); + result = base64_decode(data); } @@ -512,7 +458,7 @@ return std::string(&buffer[0]); } -#elif defined(__linux) +#elif defined(__linux) || defined(__FreeBSD_kernel__) std::string Toolbox::GetPathToExecutable() { std::vector<char> buffer(PATH_MAX + 1); @@ -551,7 +497,6 @@ std::string Toolbox::ConvertToUtf8(const std::string& source, const char* fromEncoding) { -#if BOOST_HAS_LOCALE == 1 try { return boost::locale::conv::to_utf<char>(source, fromEncoding); @@ -561,17 +506,6 @@ // Bad input string or bad encoding return ConvertToAscii(source); } -#else - IconvRabi iconv("UTF-8", fromEncoding); - try - { - return iconv.Convert(source); - } - catch (OrthancException) - { - return ConvertToAscii(source); - } -#endif } @@ -579,7 +513,7 @@ { std::string result; - result.reserve(source.size()); + result.reserve(source.size() + 1); for (size_t i = 0; i < source.size(); i++) { if (source[i] < 128 && source[i] >= 0 && !iscntrl(source[i])) @@ -803,5 +737,44 @@ result.push_back(currentItem); } + + + void Toolbox::DecodeDataUriScheme(std::string& mime, + std::string& content, + const std::string& source) + { + boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)", + boost::regex::icase /* case insensitive search */); + + boost::cmatch what; + if (regex_match(source.c_str(), what, pattern)) + { + mime = what[1]; + content = what[2]; + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void Toolbox::CreateDirectory(const std::string& path) + { + if (boost::filesystem::exists(path)) + { + if (!boost::filesystem::is_directory(path)) + { + throw OrthancException("Cannot create the directory over an existing file: " + path); + } + } + else + { + if (!boost::filesystem::create_directories(path)) + { + throw OrthancException("Unable to create the directory: " + path); + } + } + } }
--- a/Core/Toolbox.h Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Toolbox.h Wed Jun 25 12:09:38 2014 +0200 @@ -95,9 +95,11 @@ bool IsSHA1(const std::string& str); - std::string DecodeBase64(const std::string& data); + void DecodeBase64(std::string& result, + const std::string& data); - std::string EncodeBase64(const std::string& data); + void EncodeBase64(std::string& result, + const std::string& data); std::string GetPathToExecutable(); @@ -122,5 +124,11 @@ void TokenizeString(std::vector<std::string>& result, const std::string& source, char separator); + + void DecodeDataUriScheme(std::string& mime, + std::string& content, + const std::string& source); + + void CreateDirectory(const std::string& path); } }
--- a/Core/Uuid.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/Core/Uuid.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeaders.h" #include "Uuid.h" // http://stackoverflow.com/a/1626302
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DarwinCompilation.txt Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,52 @@ +This file is a complement to "INSTALL", which contains instructions +that are specific to Mac OS X (Darwin). + + +Static linking for OS X using XCode +=================================== + +The most simple way of building Orthanc under OS X consists in +statically linking against all the third-party dependencies. In this +case, no package manager such as Homebrew or MacPorts is required. +The build tool (CMake) will download the sources of all the required +packages and automatically compile them. + + +Prerequisites +------------- + +1) XCode must be installed. + +2) CMake must be installed (http://www.cmake.org/). + +3) It is assumed that Orthanc source code is placed in the folder + "~/Orthanc" and that the binaries will be compiled to + "~/OrthancBuild". + + +Prepare the build with CMake +---------------------------- + +# cd ~/OrthancBuild +# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON .. + +NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to +your version of XCode. + + +Build the Debug version of Orthanc +---------------------------------- + +# xcodebuild +# ./Debug/UnitTests + +The binaries of Orthanc are located at "~/OrthancBuild/Debug/Orthanc". + + +Build the Release version of Orthanc +------------------------------------ + +# xcodebuild -configuration Release +# ./Debug/UnitTests + +The binaries of Orthanc are located at "~/OrthancBuild/Release/Orthanc".
--- a/INSTALL Wed Apr 16 16:34:09 2014 +0200 +++ b/INSTALL Wed Jun 25 12:09:38 2014 +0200 @@ -47,6 +47,13 @@ +Native OS X Compilation +----------------------- + +See the file "DarwinCompilation.txt". + + + Native Windows build with Microsoft Visual Studio 2005 ------------------------------------------------------ @@ -71,7 +78,7 @@ the following command: # cd ~/OrthancBuild -# cmake -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake -DSTANDALONE_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc +# cmake -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DCMAKE_BUILD_TYPE=Debug ~/Orthanc # make
--- a/LinuxCompilation.txt Wed Apr 16 16:34:09 2014 +0200 +++ b/LinuxCompilation.txt Wed Jun 25 12:09:38 2014 +0200 @@ -12,6 +12,10 @@ automatically compile them. This process should work on all the Linux distributions. +We make the assumption that Orthanc source code is placed in the +folder "~/Orthanc" and that the binaries will be compiled to +"~/OrthancBuild". + To build binaries with debug information: @@ -73,6 +77,8 @@ -DUSE_SYSTEM_DCMTK=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -89,6 +95,8 @@ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -104,6 +112,8 @@ # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc Note: Have also a look at the official package: @@ -124,6 +134,8 @@ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -134,13 +146,27 @@ uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ libgoogle-glog-dev libgtest-dev libpng-dev \ libsqlite3-dev libssl-dev zlib1g-dev \ - libdcmtk2-dev libboost-all-dev libwrap0-dev + libdcmtk2-dev libboost-all-dev libwrap0-dev libcharls-dev + +With JPEG: + +# cmake "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/Orthanc + + +Without JPEG: # cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -157,6 +183,8 @@ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ ~/Orthanc @@ -169,8 +197,10 @@ gtest-devel libpng-devel libsqlite3x-devel libuuid-devel \ mongoose-devel openssl-devel jsoncpp-devel lua-devel -# cmake ~/Orthanc - +# cmake -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ + ~/Orthanc + Note: Have also a look at the official package: http://pkgs.fedoraproject.org/cgit/orthanc.git/tree/?h=f18
--- a/NEWS Wed Apr 16 16:34:09 2014 +0200 +++ b/NEWS Wed Jun 25 12:09:38 2014 +0200 @@ -1,6 +1,30 @@ Pending changes in the mainline =============================== +* Official support of OS X (Darwin) +* Options to limit the number of results for an incoming C-FIND query +* Support of kFreeBSD + + +Version 0.7.6 (2014/06/11) +========================== + +* Support of JPEG and JPEG-LS decompression +* Download DICOM images as Matlab/Octave arrays +* Precompiled headers for Microsoft Visual Studio + + +Version 0.7.5 (2014/05/08) +========================== + +* 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 +* Reuse of the previous SCU connection to avoid unecessary handshakes +* Fix problems with anonymization and modification +* Fix missing licensing terms about reuse of some code from DCMTK +* Various code refactorings Version 0.7.4 (2014/04/16)
--- a/OrthancCppClient/Instance.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/Instance.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../Core/PrecompiledHeaders.h" #include "Instance.h" #include "OrthancConnection.h" @@ -178,13 +179,13 @@ const void* Instance::GetBuffer() { DownloadImage(); - return reader_->GetBuffer(); + return reader_->GetConstBuffer(); } const void* Instance::GetBuffer(unsigned int y) { DownloadImage(); - return reader_->GetBuffer(y); + return reader_->GetConstRow(y); } void Instance::DiscardImage()
--- a/OrthancCppClient/Instance.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/Instance.h Wed Jun 25 12:09:38 2014 +0200 @@ -37,7 +37,7 @@ #include "OrthancClientException.h" #include "../Core/IDynamicObject.h" -#include "../Core/FileFormats/PngReader.h" +#include "../Core/ImageFormats/PngReader.h" namespace OrthancClient {
--- a/OrthancCppClient/OrthancConnection.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/OrthancConnection.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../Core/PrecompiledHeaders.h" #include "OrthancConnection.h" #include "../Core/Toolbox.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/OrthancCppClient.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +/** + * The sources of the C++ client library must be put in this file to + * avoid problems with precompiled headers. + **/ + +#include "../Core/ChunkedBuffer.cpp" +#include "../Core/Enumerations.cpp" +#include "../Core/HttpClient.cpp" +#include "../Core/ImageFormats/ImageAccessor.cpp" +#include "../Core/ImageFormats/ImageBuffer.cpp" +#include "../Core/ImageFormats/PngReader.cpp" +#include "../Core/MultiThreading/ArrayFilledByThreads.cpp" +#include "../Core/MultiThreading/SharedMessageQueue.cpp" +#include "../Core/MultiThreading/ThreadedCommandProcessor.cpp" +#include "../Core/OrthancException.cpp" +#include "../Core/Toolbox.cpp" +#include "../OrthancCppClient/Instance.cpp" +#include "../OrthancCppClient/OrthancConnection.cpp" +#include "../OrthancCppClient/Patient.cpp" +#include "../OrthancCppClient/Series.cpp" +#include "../OrthancCppClient/Study.cpp"
--- a/OrthancCppClient/Patient.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/Patient.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../Core/PrecompiledHeaders.h" #include "Patient.h" #include "OrthancConnection.h"
--- a/OrthancCppClient/Series.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/Series.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../Core/PrecompiledHeaders.h" #include "Series.h" #include "OrthancConnection.h"
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1486,12 +1486,12 @@ LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion() { - return "0.7.0.4"; + return "0.7.0.6"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion() { - return "0.7.4"; + return "0.7.6"; } LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str)
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Jun 25 12:09:38 2014 +0200 @@ -507,28 +507,35 @@ * The image is graylevel. Each pixel is signed and stored in two bytes. * **/ - PixelFormat_SignedGrayscale16 = 3, + PixelFormat_SignedGrayscale16 = 5, /** * @brief Color image in RGB24 format. * - * Color image in RGB24 format. + * This format describes a color image. The pixels are stored in 3 consecutive bytes. The memory layout is RGB. * **/ - PixelFormat_RGB24 = 0, + PixelFormat_RGB24 = 1, + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 consecutive bytes. The memory layout is RGBA. + * + **/ + PixelFormat_RGBA32 = 2, /** * @brief Graylevel 8bpp image. * * The image is graylevel. Each pixel is unsigned and stored in one byte. * **/ - PixelFormat_Grayscale8 = 1, + PixelFormat_Grayscale8 = 3, /** * @brief Graylevel, unsigned 16bpp image. * * The image is graylevel. Each pixel is unsigned and stored in two bytes. * **/ - PixelFormat_Grayscale16 = 2 + PixelFormat_Grayscale16 = 4 }; } @@ -549,28 +556,28 @@ * Truncation to the [-32768, 32767] range. * **/ - ImageExtractionMode_Int16 = 3, + ImageExtractionMode_Int16 = 4, /** * @brief Rescaled to 8bpp. * * The minimum value of the image is set to 0, and its maximum value is set to 255. * **/ - ImageExtractionMode_Preview = 0, + ImageExtractionMode_Preview = 1, /** * @brief Truncation to the [0, 255] range. * * Truncation to the [0, 255] range. * **/ - ImageExtractionMode_UInt8 = 1, + ImageExtractionMode_UInt8 = 2, /** * @brief Truncation to the [0, 65535] range. * * Truncation to the [0, 65535] range. * **/ - ImageExtractionMode_UInt16 = 2 + ImageExtractionMode_UInt16 = 3 }; }
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Wed Jun 25 12:09:38 2014 +0200 @@ -1,7 +1,7 @@ #include <winver.h> VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,4 + FILEVERSION 0,7,0,6 PRODUCTVERSION 0,7,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,10 +10,10 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.4" + VALUE "Comments", "Release 0.7.6" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.4" + VALUE "FileVersion", "0.7.0.6" VALUE "InternalName", "OrthancClient" VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/"
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Wed Jun 25 12:09:38 2014 +0200 @@ -1,7 +1,7 @@ #include <winver.h> VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,4 + FILEVERSION 0,7,0,6 PRODUCTVERSION 0,7,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,10 +10,10 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.4" + VALUE "Comments", "Release 0.7.6" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.4" + VALUE "FileVersion", "0.7.0.6" VALUE "InternalName", "OrthancClient" VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/"
--- a/OrthancCppClient/SharedLibrary/Product.json Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/Product.json Wed Jun 25 12:09:38 2014 +0200 @@ -4,5 +4,5 @@ "Company" : "CHU of Liege", "Copyright" : "(c) 2012-2014, Sebastien Jodogne, CHU of Liege", "Legal" : "Licensing information is available on https://code.google.com/p/orthanc/", - "Version" : "0.7.4" + "Version" : "0.7.6" }
--- a/OrthancCppClient/Study.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancCppClient/Study.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../Core/PrecompiledHeaders.h" #include "Study.h" #include "OrthancConnection.h"
--- a/OrthancServer/DatabaseWrapper.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeadersServer.h" #include "DatabaseWrapper.h" #include "../Core/DicomFormat/DicomArray.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomModification.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,297 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersServer.h" +#include "DicomModification.h" + +#include "../Core/OrthancException.h" +#include "FromDcmtkBridge.h" + +#include <memory> // For std::auto_ptr +#include <glog/logging.h> + +namespace Orthanc +{ + 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; + } + + void DicomModification::Keep(const DicomTag& tag) + { + removals_.erase(tag); + replacements_.erase(tag); + } + + void DicomModification::Remove(const DicomTag& tag) + { + removals_.insert(tag); + replacements_.erase(tag); + } + + bool DicomModification::IsRemoved(const DicomTag& tag) const + { + return removals_.find(tag) != removals_.end(); + } + + void DicomModification::Replace(const DicomTag& tag, + const std::string& value) + { + removals_.erase(tag); + replacements_[tag] = value; + } + + bool DicomModification::IsReplaced(const DicomTag& tag) const + { + return replacements_.find(tag) != replacements_.end(); + } + + const std::string& 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; + } + } + + void DicomModification::SetRemovePrivateTags(bool removed) + { + removePrivateTags_ = removed; + } + + void DicomModification::SetLevel(ResourceType level) + { + uidMap_.clear(); + level_ = level; + } + + void DicomModification::SetupAnonymization() + { + removals_.clear(); + replacements_.clear(); + removePrivateTags_ = true; + level_ = ResourceType_Patient; + uidMap_.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 + replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); + + // Set the PatientIdentityRemoved tag + replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); + + // (*) Choose a random patient name and ID + std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); + replacements_[DICOM_TAG_PATIENT_ID] = patientId; + replacements_[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); + } + + 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 (level_ > ResourceType_Patient && IsReplaced(DICOM_TAG_PATIENT_ID)) + { + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ > ResourceType_Study && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ > ResourceType_Series && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadRequest); + } + + // (1) Remove the private tags, if need be + if (removePrivateTags_) + { + toModify.RemovePrivateTags(); + } + + // (2) Remove the tags specified by the user + for (Removals::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) + { + MapDicomIdentifier(toModify, ResourceType_Study); + } + + if (level_ <= ResourceType_Series) + { + MapDicomIdentifier(toModify, ResourceType_Series); + } + + if (level_ <= ResourceType_Instance) // Always true + { + MapDicomIdentifier(toModify, ResourceType_Instance); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomModification.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,96 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ParsedDicomFile.h" + +namespace Orthanc +{ + class DicomModification + { + /** + * Process: + * (1) Remove private tags + * (2) Remove tags specified by the user + * (3) Replace tags + **/ + + private: + typedef std::set<DicomTag> Removals; + typedef std::map<DicomTag, std::string> Replacements; + typedef std::map< std::pair<ResourceType, std::string>, std::string> UidMap; + + Removals removals_; + Replacements replacements_; + bool removePrivateTags_; + ResourceType level_; + UidMap uidMap_; + + void MapDicomIdentifier(ParsedDicomFile& dicom, + ResourceType level); + + public: + DicomModification(); + + void Keep(const DicomTag& tag); + + void Remove(const DicomTag& tag); + + bool IsRemoved(const DicomTag& tag) const; + + void Replace(const DicomTag& tag, + const std::string& value); + + bool IsReplaced(const DicomTag& tag) const; + + const std::string& GetReplacement(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); + }; +}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "DicomFindAnswers.h" #include "../FromDcmtkBridge.h"
--- a/OrthancServer/DicomProtocol/DicomServer.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "DicomServer.h" #include "../../Core/OrthancException.h" @@ -115,7 +116,7 @@ LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICOM); LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE); -#elif defined(__linux) +#elif defined(__linux) || defined(__FreeBSD_kernel__) std::string path = DCMTK_DICTIONARY_DIR; const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); @@ -414,7 +415,7 @@ return true; } - return Orthanc::IsSameAETitle(aet, GetApplicationEntityTitle()); + return Configuration::IsSameAETitle(aet, GetApplicationEntityTitle()); } }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,55 @@ **/ + +/*========================================================================= + + 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/OrthancException.h" @@ -58,8 +107,29 @@ #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 @@ -103,55 +173,38 @@ } - void DicomUserConnection::CopyParameters(const DicomUserConnection& other) + static void RegisterStorageSOPClass(T_ASC_Parameters* params, + unsigned int& presentationContextId, + const std::string& sopClass, + const char* asPreferred[], + std::vector<const char*>& asFallback) { - Close(); - localAet_ = other.localAet_; - distantAet_ = other.distantAet_; - distantHost_ = other.distantHost_; - distantPort_ = other.distantPort_; - manufacturer_ = other.manufacturer_; - preferredTransferSyntax_ = other.preferredTransferSyntax_; + 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) { - // Fallback transfer syntaxes + // 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); - - // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE - std::vector<std::string> transferSyntaxes; - transferSyntaxes.push_back(UID_VerificationSOPClass); - transferSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - transferSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - transferSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - - // TODO: Allow the set below to be configured - std::set<std::string> uselessSyntaxes; - uselessSyntaxes.insert(UID_BlendingSoftcopyPresentationStateStorage); - uselessSyntaxes.insert(UID_GrayscaleSoftcopyPresentationStateStorage); - uselessSyntaxes.insert(UID_ColorSoftcopyPresentationStateStorage); - uselessSyntaxes.insert(UID_PseudoColorSoftcopyPresentationStateStorage); - - // Add the transfer syntaxes for C-STORE - for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) - { - // Test to make some room to allow the ECHO and FIND requests - if (uselessSyntaxes.find(dcmShortSCUStorageSOPClassUIDs[i]) == uselessSyntaxes.end()) - { - transferSyntaxes.push_back(dcmShortSCUStorageSOPClassUIDs[i]); - } - } - - // Flatten the fallback transfer syntaxes array - const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; - 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 @@ -160,19 +213,28 @@ asFallback.push_back(it->c_str()); } + CheckStorageSOPClassesInvariant(); unsigned int presentationContextId = 1; - for (size_t i = 0; i < transferSyntaxes.size(); i++) + + for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); + it != reservedStorageSOPClasses_.end(); ++it) { - Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, - transferSyntaxes[i].c_str(), asPreferred, 1)); - presentationContextId += 2; + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } - if (asFallback.size() > 0) - { - Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, - transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size())); - presentationContextId += 2; - } + 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); } } @@ -192,8 +254,16 @@ 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 - // negociated, depending on the transfer syntax of this instance + // negotiated, depending on the transfer syntax of this instance DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); const std::string syntax(xfer.getXferID()); bool isGeneric = IsGenericTransferSyntax(syntax); @@ -201,8 +271,8 @@ if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) { // Making a generic-to-specific or specific-to-generic change of - // the transfer syntax. Renegociate the connection. - LOG(INFO) << "Renegociating a C-Store association due to a change in the transfer syntax"; + // the transfer syntax. Renegotiate the connection. + LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; if (isGeneric) { @@ -212,7 +282,11 @@ { connection.SetPreferredTransferSyntax(syntax); } + } + if (!connection.IsOpen()) + { + LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; connection.Open(); } @@ -232,7 +306,7 @@ if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); if (!modalityName) modalityName = "unknown SOP class"; throw OrthancException("DicomUserConnection: No presentation context for modality " + - std::string(modalityName)); + std::string(modalityName)); } // Prepare the transmission of data @@ -288,88 +362,88 @@ std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); switch (model) { - case FindRootModel_Patient: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); - sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; + case FindRootModel_Patient: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); + sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // Accession number + if (!fields.HasTag(0x0008, 0x0050)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - // Patient ID - if (!fields.HasTag(0x0010, 0x0020)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); + // Patient ID + if (!fields.HasTag(0x0010, 0x0020)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); - break; + break; - case FindRootModel_Study: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + case FindRootModel_Study: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // 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), ""); + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - break; + break; - case FindRootModel_Series: - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + case FindRootModel_Series: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // 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), ""); + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); + // Series instance UID + if (!fields.HasTag(0x0020, 0x000e)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); - break; + break; - case FindRootModel_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"); - } + case FindRootModel_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; + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); + // 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), ""); + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); + // Series instance UID + if (!fields.HasTag(0x0020, 0x000e)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); - // SOP Instance UID - if (!fields.HasTag(0x0008, 0x0018)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); + // SOP Instance UID + if (!fields.HasTag(0x0008, 0x0018)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); - break; + break; - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); } // Figure out which of the accepted presentation contexts should be used @@ -501,6 +575,35 @@ } + 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), @@ -514,6 +617,14 @@ 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() @@ -521,6 +632,16 @@ Close(); } + + void DicomUserConnection::Connect(const RemoteModalityParameters& parameters) + { + SetDistantApplicationEntityTitle(parameters.GetApplicationEntityTitle()); + SetDistantHost(parameters.GetHost()); + SetDistantPort(parameters.GetPort()); + SetDistantManufacturer(parameters.GetManufacturer()); + } + + void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) { if (localAet_ != aet) @@ -594,6 +715,11 @@ return; } + LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() + << "\" to AET \"" << GetDistantApplicationEntityTitle() << "\" on host " + << GetDistantHost() << ":" << GetDistantPort() + << " (manufacturer: " << EnumerationToString(GetDistantManufacturer()) << ")"; + Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_)); Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); @@ -607,11 +733,11 @@ char distantHostAndPort[HOST_NAME_MAX]; #ifdef _MSC_VER - _snprintf + _snprintf #else - snprintf + snprintf #endif - (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); + (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_); Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort)); @@ -742,4 +868,60 @@ dcmConnectionTimeout.set(seconds); } + + 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_.size() > 0); // 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 Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Wed Jun 25 12:09:38 2014 +0200 @@ -34,10 +34,12 @@ #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 { @@ -62,6 +64,9 @@ std::string distantHost_; uint16_t distantPort_; ModalityManufacturer manufacturer_; + std::set<std::string> storageSOPClasses_; + std::list<std::string> reservedStorageSOPClasses_; + std::set<std::string> defaultStorageSOPClasses_; void CheckIsOpen() const; @@ -74,12 +79,16 @@ void Move(const std::string& targetAet, const DicomMap& fields); + void ResetStorageSOPClasses(); + + void CheckStorageSOPClassesInvariant() const; + public: DicomUserConnection(); ~DicomUserConnection(); - void CopyParameters(const DicomUserConnection& other); + void Connect(const RemoteModalityParameters& parameters); void SetLocalApplicationEntityTitle(const std::string& aet); @@ -125,6 +134,8 @@ return preferredTransferSyntax_; } + void AddStorageSOPClass(const char* sop); + void Open(); void Close();
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Jun 25 12:09:38 2014 +0200 @@ -47,7 +47,13 @@ { } - virtual void Handle(DicomFindAnswers& answers, + /** + * 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& callingAETitle) = 0; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,107 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../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) + { + } + + void RemoteModalityParameters::SetPort(int port) + { + if (port <= 0 || port >= 65535) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + port_ = port; + } + + 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 + { + SetPort(portValue.asInt()); + } + catch (std::runtime_error /* error inside JsonCpp */) + { + try + { + SetPort(boost::lexical_cast<int>(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())); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ServerEnumerations.h" + +#include <string> +#include <json/json.h> + +namespace Orthanc +{ + class RemoteModalityParameters + { + // TODO Use the flyweight pattern for this class + + private: + std::string aet_; + std::string host_; + int port_; + ModalityManufacturer manufacturer_; + + public: + RemoteModalityParameters(); + + 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; + } + + int GetPort() const + { + return port_; + } + + void SetPort(int 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; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,179 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "ReusableDicomUserConnection.h" + +#include "../../Core/OrthancException.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + static boost::posix_time::ptime Now() + { + return boost::posix_time::microsec_clock::local_time(); + } + + void ReusableDicomUserConnection::Open(const std::string& remoteAet, + const std::string& address, + int port, + ModalityManufacturer manufacturer) + { + if (connection_ != NULL && + connection_->GetDistantApplicationEntityTitle() == remoteAet && + connection_->GetDistantHost() == address && + connection_->GetDistantPort() == port && + connection_->GetDistantManufacturer() == manufacturer) + { + // The current connection can be reused + LOG(INFO) << "Reusing the previous SCU connection"; + return; + } + + Close(); + + connection_ = new DicomUserConnection(); + connection_->SetLocalApplicationEntityTitle(localAet_); + connection_->SetDistantApplicationEntityTitle(remoteAet); + connection_->SetDistantHost(address); + connection_->SetDistantPort(port); + connection_->SetDistantManufacturer(manufacturer); + 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& aet, + const std::string& address, + int port, + ModalityManufacturer manufacturer) : + ::Orthanc::Locker(that) + { + that.Open(aet, address, port, manufacturer); + connection_ = that.connection_; + } + + + ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, + const RemoteModalityParameters& remote) : + ::Orthanc::Locker(that) + { + that.Open(remote.GetApplicationEntityTitle(), remote.GetHost(), + remote.GetPort(), remote.GetManufacturer()); + 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 + localAet_("ORTHANC") + { + lastUse_ = Now(); + continue_ = true; + closeThread_ = boost::thread(CloseThread, this); + } + + ReusableDicomUserConnection::~ReusableDicomUserConnection() + { + continue_ = false; + closeThread_.join(); + Close(); + } + + void ReusableDicomUserConnection::SetMillisecondsBeforeClose(unsigned int ms) + { + boost::mutex::scoped_lock lock(mutex_); + timeBeforeClose_ = boost::posix_time::milliseconds(ms); + } + + void ReusableDicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) + { + boost::mutex::scoped_lock lock(mutex_); + Close(); + localAet_ = aet; + } + + void ReusableDicomUserConnection::Lock() + { + mutex_.lock(); + } + + void ReusableDicomUserConnection::Unlock() + { + lastUse_ = Now(); + mutex_.unlock(); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,103 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "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_; + std::string localAet_; + + void Open(const std::string& remoteAet, + const std::string& address, + int port, + ModalityManufacturer manufacturer); + + 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 RemoteModalityParameters& remote); + + Locker(ReusableDicomUserConnection& that, + const std::string& aet, + const std::string& address, + int port, + ModalityManufacturer manufacturer); + + DicomUserConnection& GetConnection(); + }; + + ReusableDicomUserConnection(); + + virtual ~ReusableDicomUserConnection(); + + unsigned int GetMillisecondsBeforeClose() const + { + return static_cast<unsigned int>(timeBeforeClose_.total_milliseconds()); + } + + void SetMillisecondsBeforeClose(unsigned int ms); + + const std::string& GetLocalApplicationEntityTitle() const; + + void SetLocalApplicationEntityTitle(const std::string& aet); + }; +} +
--- a/OrthancServer/FromDcmtkBridge.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -31,57 +31,19 @@ -/*========================================================================= - - 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 "Internals/DicomImageDecoder.h" + #include "FromDcmtkBridge.h" - #include "ToDcmtkBridge.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" -#include "../Core/FileFormats/PngWriter.h" +#include "../Core/ImageFormats/PngWriter.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomString.h" #include "../Core/DicomFormat/DicomNullValue.h" @@ -131,51 +93,8 @@ #include <dcmtk/dcmdata/dcostrmb.h> -static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; - - - namespace Orthanc { - void ParsedDicomFile::Setup(const char* buffer, size_t size) - { - DcmInputBufferStream is; - if (size > 0) - { - is.setBuffer(buffer, size); - } - is.setEos(); - - file_.reset(new DcmFileFormat); - file_->transferInit(); - if (!file_->read(is).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - file_->loadAllDataIntoMemory(); - 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') @@ -196,693 +115,6 @@ 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; - } - } - - - static void AnswerDicomField(RestApiOutput& output, - DcmElement& element, - E_TransferSyntax transferSyntax) - { - // This element is nor a sequence, neither a pixel-data - std::string buffer; - buffer.resize(65536); - Uint32 length = element.getLength(transferSyntax); - Uint32 offset = 0; - - output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL); - - while (offset < length) - { - Uint32 nbytes; - if (length - offset < buffer.size()) - { - nbytes = length - offset; - } - else - { - nbytes = buffer.size(); - } - - OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes); - - if (cond.good()) - { - output.GetLowLevelOutput().Send(&buffer[0], nbytes); - offset += nbytes; - } - else - { - LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); - return; - } - } - - output.MarkLowLevelOutputDone(); - } - - - 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"); - AnswerDicomField(output, *element, transferSyntax); - } - } - } - 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) - { - AnswerDicomField(output, *element, transferSyntax); - } - } - - void ParsedDicomFile::SendPathValue(RestApiOutput& output, - const UriComponents& uri) - { - DcmItem* dicom = file_->getDataset(); - E_TransferSyntax transferSyntax = 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); - } - } - - - - - - static DcmElement* CreateElementForTag(const DicomTag& tag) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - - 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. - * 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); - - - /** - * 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; - } - - throw OrthancException(ErrorCode_InternalError); - } - - - - static void FillElementWithString(DcmElement& element, - const DicomTag& tag, - const std::string& value) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - 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(value.c_str()).good(); - break; - } - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - { - ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good(); - break; - } - - case EVR_SS: // signed short - { - ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good(); - break; - } - - case EVR_UL: // unsigned long - { - ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good(); - break; - } - - case EVR_US: // unsigned short - { - ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good(); - break; - } - - case EVR_FL: // float single-precision - { - ok = element.putFloat32(boost::lexical_cast<float>(value)).good(); - break; - } - - case EVR_FD: // float double-precision - { - ok = element.putFloat64(boost::lexical_cast<double>(value)).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); - } - } - - - void ParsedDicomFile::Remove(const DicomTag& tag) - { - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = file_->getDataset()->remove(key); - if (element != NULL) - { - delete element; - } - } - - - - void ParsedDicomFile::RemovePrivateTags() - { - typedef std::list<DcmElement*> Tags; - - Tags privateTags; - - DcmDataset& dataset = *file_->getDataset(); - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - DcmTag tag(element->getTag()); - if (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK - tag.getPrivateCreator() != NULL) - { - privateTags.push_back(element); - } - } - - for (Tags::iterator it = privateTags.begin(); - it != privateTags.end(); ++it) - { - DcmElement* tmp = dataset.remove(*it); - if (tmp != NULL) - { - delete tmp; - } - } - } - - - - void ParsedDicomFile::Insert(const DicomTag& tag, - const std::string& value) - { - std::auto_ptr<DcmElement> element(CreateElementForTag(tag)); - FillElementWithString(*element, tag, value); - - if (!file_->getDataset()->insert(element.release(), false, false).good()) - { - // This field already exists - throw OrthancException(ErrorCode_InternalError); - } - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const std::string& value, - DicomReplaceMode mode) - { - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = NULL; - - if (!file_->getDataset()->findAndGetElement(key, element).good() || - element == NULL) - { - // This field does not exist, act wrt. the specified "mode" - switch (mode) - { - case DicomReplaceMode_InsertIfAbsent: - Insert(tag, value); - break; - - case DicomReplaceMode_ThrowIfAbsent: - throw OrthancException(ErrorCode_InexistentItem); - - case DicomReplaceMode_IgnoreIfAbsent: - return; - } - } - else - { - FillElementWithString(*element, tag, value); - } - - - /** - * 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, value, DicomReplaceMode_InsertIfAbsent); - - if (tag == DICOM_TAG_SOP_INSTANCE_UID) - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent); - } - - - void ParsedDicomFile::Answer(RestApiOutput& output) - { - std::string serialized; - if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, 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 = *file_->getDataset(); - DcmElement* element = NULL; - if (!dataset.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element)); - - if (v.get() == NULL) - { - value = ""; - } - else - { - value = v->AsString(); - } - - 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 FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) { target.Clear(); @@ -1196,345 +428,6 @@ } - static void ExtractPngImageColorPreview(std::string& result, - DicomIntegerPixelAccessor& accessor) - { - assert(accessor.GetChannelCount() == 3); - PngWriter w; - - std::vector<uint8_t> image(accessor.GetWidth() * accessor.GetHeight() * 3, 0); - uint8_t* pixel = &image[0]; - - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - for (unsigned int x = 0; x < accessor.GetWidth(); x++) - { - for (unsigned int c = 0; c < 3; c++, pixel++) - { - int32_t v = accessor.GetValue(x, y, c); - if (v < 0) - *pixel = 0; - else if (v > 255) - *pixel = 255; - else - *pixel = v; - } - } - } - - w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetWidth() * 3, PixelFormat_RGB24, &image[0]); - } - - - static void ExtractPngImageGrayscalePreview(std::string& result, - DicomIntegerPixelAccessor& accessor) - { - assert(accessor.GetChannelCount() == 1); - PngWriter w; - - int32_t min, max; - accessor.GetExtremeValues(min, max); - - std::vector<uint8_t> image(accessor.GetWidth() * accessor.GetHeight(), 0); - if (min != max) - { - uint8_t* pixel = &image[0]; - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++) - { - int32_t v = accessor.GetValue(x, y); - *pixel = static_cast<uint8_t>( - boost::math::lround(static_cast<float>(v - min) / - static_cast<float>(max - min) * 255.0f)); - } - } - } - - w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetWidth(), PixelFormat_Grayscale8, &image[0]); - } - - - template <typename T> - static void ExtractPngImageTruncate(std::string& result, - DicomIntegerPixelAccessor& accessor, - PixelFormat format) - { - assert(accessor.GetChannelCount() == 1); - - PngWriter w; - - std::vector<T> image(accessor.GetWidth() * accessor.GetHeight(), 0); - T* pixel = &image[0]; - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - for (unsigned int x = 0; x < accessor.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.GetWidth(), accessor.GetHeight(), - accessor.GetWidth() * sizeof(T), format, &image[0]); - } - - - static bool DecodePsmctRle1(std::string& output, - DcmDataset& dataset) - { - static const DicomTag tagContent(0x07a1, 0x100a); - static const DicomTag tagCompressionType(0x07a1, 0x1011); - - DcmElement* e; - char* c; - - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(tagCompressionType), e).good() || - e == NULL || - !e->isaString() || - !e->getString(c).good() || - c == NULL || - strcmp("PMSCT_RLE1", c)) - { - return false; - } - - // OK, this is a custom RLE encoding from Philips. Get the pixel - // data from the appropriate private DICOM tag. - Uint8* pixData = NULL; - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(tagContent), 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; - } - - - void FromDcmtkBridge::ExtractPngImage(std::string& result, - DcmDataset& dataset, - unsigned int frame, - ImageExtractionMode mode) - { - // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data - - std::auto_ptr<DicomIntegerPixelAccessor> accessor; - - DicomMap m; - FromDcmtkBridge::Convert(m, dataset); - - std::string privateContent; - - DcmElement* e; - if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && - e != NULL) - { - Uint8* pixData = NULL; - if (e->getUint8Array(pixData) == EC_Normal) - { - accessor.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); - accessor->SetCurrentFrame(frame); - } - } - else if (DecodePsmctRle1(privateContent, dataset)) - { - LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; - Uint8* pixData = NULL; - if (privateContent.size() > 0) - pixData = reinterpret_cast<Uint8*>(&privateContent[0]); - accessor.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size())); - accessor->SetCurrentFrame(frame); - } - - if (accessor.get() == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - PixelFormat format; - bool supported = false; - - if (accessor->GetChannelCount() == 1) - { - switch (mode) - { - case ImageExtractionMode_Preview: - supported = true; - format = PixelFormat_Grayscale8; - break; - - case ImageExtractionMode_UInt8: - supported = true; - format = PixelFormat_Grayscale8; - break; - - case ImageExtractionMode_UInt16: - supported = true; - format = PixelFormat_Grayscale16; - break; - - case ImageExtractionMode_Int16: - supported = true; - format = PixelFormat_SignedGrayscale16; - break; - - default: - supported = false; - break; - } - } - else if (accessor->GetChannelCount() == 3) - { - switch (mode) - { - case ImageExtractionMode_Preview: - supported = true; - format = PixelFormat_RGB24; - break; - - default: - supported = false; - break; - } - } - - if (!supported) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if (accessor.get() == NULL || - accessor->GetWidth() == 0 || - accessor->GetHeight() == 0) - { - PngWriter w; - w.WriteToMemory(result, 0, 0, 0, format, NULL); - } - else - { - switch (mode) - { - case ImageExtractionMode_Preview: - if (format == PixelFormat_Grayscale8) - ExtractPngImageGrayscalePreview(result, *accessor); - else - ExtractPngImageColorPreview(result, *accessor); - break; - - case ImageExtractionMode_UInt8: - ExtractPngImageTruncate<uint8_t>(result, *accessor, format); - break; - - case ImageExtractionMode_UInt16: - ExtractPngImageTruncate<uint16_t>(result, *accessor, format); - break; - - case ImageExtractionMode_Int16: - ExtractPngImageTruncate<int16_t>(result, *accessor, format); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - } - - - void FromDcmtkBridge::ExtractPngImage(std::string& result, - const std::string& dicomContent, - unsigned int frame, - ImageExtractionMode mode) - { - DcmInputBufferStream is; - if (dicomContent.size() > 0) - { - is.setBuffer(&dicomContent[0], dicomContent.size()); - } - is.setEos(); - - DcmFileFormat dicom; - if (dicom.read(is).good()) - { - ExtractPngImage(result, *dicom.getDataset(), frame, mode); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - std::string FromDcmtkBridge::GetName(const DicomTag& t) { @@ -1653,25 +546,25 @@ } - std::string FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel level) + std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) { char uid[100]; switch (level) { - case DicomRootLevel_Patient: + 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 DicomRootLevel_Instance: + case ResourceType_Instance: return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); - case DicomRootLevel_Series: + case ResourceType_Series: return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); - case DicomRootLevel_Study: + case ResourceType_Study: return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); default:
--- a/OrthancServer/FromDcmtkBridge.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Wed Jun 25 12:09:38 2014 +0200 @@ -32,92 +32,15 @@ #pragma once -#include "../Core/DicomFormat/DicomInstanceHasher.h" -#include "../Core/RestApi/RestApiOutput.h" -#include "../Core/Toolbox.h" +#include "ServerEnumerations.h" + +#include "../Core/DicomFormat/DicomMap.h" #include <dcmtk/dcmdata/dcdatset.h> -#include <dcmtk/dcmdata/dcfilefo.h> #include <json/json.h> -#include <memory> namespace Orthanc { - enum DicomRootLevel - { - DicomRootLevel_Patient, - DicomRootLevel_Study, - DicomRootLevel_Series, - DicomRootLevel_Instance - }; - - enum DicomReplaceMode - { - DicomReplaceMode_InsertIfAbsent, - DicomReplaceMode_ThrowIfAbsent, - DicomReplaceMode_IgnoreIfAbsent - }; - - class ParsedDicomFile : public IDynamicObject - { - private: - std::auto_ptr<DcmFileFormat> file_; - - ParsedDicomFile(DcmFileFormat& other) : - file_(dynamic_cast<DcmFileFormat*>(other.clone())) - { - } - - void Setup(const char* content, - size_t size); - - public: - ParsedDicomFile(const char* content, - size_t size) - { - Setup(content, size); - } - - ParsedDicomFile(const std::string& content) - { - if (content.size() == 0) - Setup(NULL, 0); - else - Setup(&content[0], content.size()); - } - - DcmFileFormat& GetDicom() - { - return *file_; - } - - ParsedDicomFile* Clone() - { - return new ParsedDicomFile(*file_); - } - - void SendPathValue(RestApiOutput& output, - const UriComponents& uri); - - void Answer(RestApiOutput& output); - - void Remove(const DicomTag& tag); - - void Insert(const DicomTag& tag, - const std::string& value); - - void Replace(const DicomTag& tag, - const std::string& value, - DicomReplaceMode mode); - - void RemovePrivateTags(); - - bool GetTagValue(std::string& value, - const DicomTag& tag); - - DicomInstanceHasher GetHasher(); - }; - class FromDcmtkBridge { public: @@ -135,16 +58,6 @@ const std::string& path, unsigned int maxStringLength = 256); - static void ExtractPngImage(std::string& result, - DcmDataset& dataset, - unsigned int frame, - ImageExtractionMode mode); - - static void ExtractPngImage(std::string& result, - const std::string& dicomContent, - unsigned int frame, - ImageExtractionMode mode); - static std::string GetName(const DicomTag& tag); static DicomTag ParseTag(const char* name); @@ -179,7 +92,7 @@ static void ToJson(Json::Value& result, const DicomMap& values); - static std::string GenerateUniqueIdentifier(DicomRootLevel level); + static std::string GenerateUniqueIdentifier(ResourceType level); static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset* dataSet);
--- a/OrthancServer/Internals/CommandDispatcher.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,56 @@ **/ + + +/*========================================================================= + + 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"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Internals/DicomImageDecoder.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,685 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../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/OrthancException.h" +#include "../../Core/ImageFormats/ImageProcessing.h" +#include "../../Core/ImageFormats/PngWriter.h" // TODO REMOVE THIS +#include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" +#include "../ToDcmtkBridge.h" +#include "../FromDcmtkBridge.h" + +#include <glog/logging.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_; + std::auto_ptr<ImageAccessor> fastAccessor_; + + public: + void Setup(DcmDataset& dataset, + unsigned int frame) + { + psmct_.clear(); + slowAccessor_.reset(NULL); + fastAccessor_.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); + + + /** + * If possible, create a fast ImageAccessor to the image buffer. + **/ + + + } + + 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_; + } + + bool HasFastAccessor() const + { + return fastAccessor_.get() != NULL; + } + + const ImageAccessor& GetFastAccessor() const + { + assert(HasFastAccessor()); + return *fastAccessor_; + } + }; + + + 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"); + 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 + { + ImageAccessor sourceImage; + sourceImage.AssignReadOnly(sourceFormat, + info.GetWidth(), + info.GetHeight(), + info.GetWidth() * GetBytesPerPixel(sourceFormat), + source.GetAccessor().GetPixelData()); + + 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; + } + + + bool DicomImageDecoder::DecodeAndTruncate(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame, + PixelFormat format) + { + // TODO Special case for uncompressed images + + ImageBuffer source; + if (!Decode(source, dataset, frame)) + { + 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); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Internals/DicomImageDecoder.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,81 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <dcmtk/dcmdata/dcfilefo.h> + +#include "../../Core/ImageFormats/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); + + static bool DecodePreview(ImageBuffer& target, + DcmDataset& dataset, + unsigned int frame); + }; +}
--- a/OrthancServer/Internals/FindScp.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/Internals/FindScp.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,56 @@ **/ + +/*========================================================================= + + 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" @@ -50,6 +100,7 @@ DicomFindAnswers answers_; DcmDataset* lastRequest_; const std::string* callingAETitle_; + bool noCroppingOfResults_; }; @@ -75,12 +126,12 @@ try { - data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_); + data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_); } catch (OrthancException& e) { // Internal error! - LOG(ERROR) << "IFindRequestHandler Failed: " << e.What(); + LOG(ERROR) << "C-FIND request handler has failed: " << e.What(); response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; *responseIdentifiers = NULL; return; @@ -98,12 +149,21 @@ 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 { - response->DimseStatus = STATUS_Success; + // 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; } } @@ -120,6 +180,7 @@ data.lastRequest_ = NULL; data.handler_ = &handler; data.callingAETitle_ = &callingAETitle; + data.noCroppingOfResults_ = true; OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, FindScpCallback, &data,
--- a/OrthancServer/Internals/MoveScp.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/Internals/MoveScp.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,56 @@ **/ + + +/*========================================================================= + + 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>
--- a/OrthancServer/Internals/StoreScp.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/Internals/StoreScp.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,56 @@ **/ + + +/*========================================================================= + + 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"
--- a/OrthancServer/OrthancFindRequestHandler.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -29,6 +29,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + +#include "PrecompiledHeadersServer.h" #include "OrthancFindRequestHandler.h" #include <glog/logging.h> @@ -37,6 +39,7 @@ #include "../Core/DicomFormat/DicomArray.h" #include "ServerToolbox.h" #include "OrthancInitialization.h" +#include "FromDcmtkBridge.h" namespace Orthanc { @@ -439,7 +442,26 @@ } - void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, + bool OrthancFindRequestHandler::HasReachedLimit(const DicomFindAnswers& answers, + ResourceType level) const + { + switch (level) + { + case ResourceType_Patient: + case ResourceType_Study: + case ResourceType_Series: + return (maxResults_ != 0 && answers.GetSize() >= maxResults_); + + case ResourceType_Instance: + return (maxInstances_ != 0 && answers.GetSize() >= maxInstances_); + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, const std::string& callingAETitle) { @@ -450,13 +472,14 @@ ModalityManufacturer manufacturer; { - std::string symbolicName, address; - int port; + RemoteModalityParameters modality; - if (!LookupDicomModalityUsingAETitle(callingAETitle, symbolicName, address, port, manufacturer)) + if (!Configuration::LookupDicomModalityUsingAETitle(modality, callingAETitle)) { throw OrthancException("Unknown modality"); } + + manufacturer = modality.GetManufacturer(); } @@ -576,6 +599,12 @@ if (Matches(info, query)) { + if (HasReachedLimit(answers, level)) + { + // Too many results, stop before recording this new match + return false; + } + AddAnswer(answers, info, query); } } @@ -585,6 +614,8 @@ // This resource has probably been deleted during the find request } } + + return true; // All the matching resources have been returned } }
--- a/OrthancServer/OrthancFindRequestHandler.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.h Wed Jun 25 12:09:38 2014 +0200 @@ -41,15 +41,42 @@ { private: ServerContext& context_; + unsigned int maxResults_; + unsigned int maxInstances_; + + bool HasReachedLimit(const DicomFindAnswers& answers, + ResourceType level) const; public: OrthancFindRequestHandler(ServerContext& context) : - context_(context) + context_(context), + maxResults_(0), + maxInstances_(0) { } - virtual void Handle(DicomFindAnswers& answers, + virtual bool Handle(DicomFindAnswers& answers, const DicomMap& input, const std::string& callingAETitle); + + unsigned int GetMaxResults() const + { + return maxResults_; + } + + void SetMaxResults(unsigned int results) + { + maxResults_ = results; + } + + unsigned int GetMaxInstances() const + { + return maxInstances_; + } + + void SetMaxInstances(unsigned int instances) + { + maxInstances_ = instances; + } }; }
--- a/OrthancServer/OrthancInitialization.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeadersServer.h" #include "OrthancInitialization.h" #include "../Core/HttpClient.h" @@ -44,12 +45,24 @@ #include <boost/thread.hpp> #include <glog/logging.h> + +#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 std::auto_ptr<Json::Value> configuration_; static boost::filesystem::path defaultDirectory_; + static void ReadGlobalConfiguration(const char* configurationFile) { configuration_.reset(new Json::Value); @@ -180,6 +193,16 @@ RegisterUserContentType(); DicomServer::InitializeDictionary(); + +#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 } @@ -189,12 +212,22 @@ boost::mutex::scoped_lock lock(globalMutex_); HttpClient::GlobalFinalize(); configuration_.reset(NULL); + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + // Unregister JPEG-LS codecs + DJLSDecoderRegistration::cleanup(); +#endif + +#if ORTHANC_JPEG_ENABLED == 1 + // Unregister JPEG codecs + DJDecoderRegistration::cleanup(); +#endif } - std::string GetGlobalStringParameter(const std::string& parameter, - const std::string& defaultValue) + std::string Configuration::GetGlobalStringParameter(const std::string& parameter, + const std::string& defaultValue) { boost::mutex::scoped_lock lock(globalMutex_); @@ -209,8 +242,8 @@ } - int GetGlobalIntegerParameter(const std::string& parameter, - int defaultValue) + int Configuration::GetGlobalIntegerParameter(const std::string& parameter, + int defaultValue) { boost::mutex::scoped_lock lock(globalMutex_); @@ -224,8 +257,9 @@ } } - bool GetGlobalBoolParameter(const std::string& parameter, - bool defaultValue) + + bool Configuration::GetGlobalBoolParameter(const std::string& parameter, + bool defaultValue) { boost::mutex::scoped_lock lock(globalMutex_); @@ -240,13 +274,8 @@ } - - - void GetDicomModalityUsingSymbolicName(const std::string& name, - std::string& aet, - std::string& address, - int& port, - ModalityManufacturer& manufacturer) + void Configuration::GetDicomModalityUsingSymbolicName(RemoteModalityParameters& modality, + const std::string& name) { boost::mutex::scoped_lock lock(globalMutex_); @@ -257,57 +286,27 @@ const Json::Value& modalities = (*configuration_) ["DicomModalities"]; if (modalities.type() != Json::objectValue || - !modalities.isMember(name) || - (modalities[name].size() != 3 && modalities[name].size() != 4)) + !modalities.isMember(name)) { throw OrthancException(ErrorCode_BadFileFormat); } try { - aet = modalities[name].get(0u, "").asString(); - address = modalities[name].get(1u, "").asString(); - - const Json::Value& portValue = modalities[name].get(2u, ""); - try - { - port = portValue.asInt(); - } - catch (std::runtime_error /* error inside JsonCpp */) - { - try - { - port = boost::lexical_cast<int>(portValue.asString()); - } - catch (boost::bad_lexical_cast) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - if (modalities[name].size() == 4) - { - manufacturer = StringToModalityManufacturer(modalities[name].get(3u, "").asString()); - } - else - { - manufacturer = ModalityManufacturer_Generic; - } + modality.FromJson(modalities[name]); } catch (OrthancException& e) { LOG(ERROR) << "Syntax error in the definition of modality \"" << name << "\". Please check your configuration file."; - throw e; + throw; } } - void GetOrthancPeer(const std::string& name, - std::string& url, - std::string& username, - std::string& password) + void Configuration::GetOrthancPeer(OrthancPeerParameters& peer, + const std::string& name) { boost::mutex::scoped_lock lock(globalMutex_); @@ -325,40 +324,13 @@ throw OrthancException(ErrorCode_BadFileFormat); } - try - { - url = modalities[name].get(0u, "").asString(); - - if (modalities[name].size() == 1) - { - username = ""; - password = ""; - } - else if (modalities[name].size() == 3) - { - username = modalities[name].get(1u, "").asString(); - password = modalities[name].get(2u, "").asString(); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - catch (...) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (url.size() != 0 && url[url.size() - 1] != '/') - { - url += '/'; - } + peer.FromJson(modalities[name]); } catch (OrthancException& e) { LOG(ERROR) << "Syntax error in the definition of peer \"" << name << "\". Please check your configuration file."; - throw e; + throw; } } @@ -403,7 +375,7 @@ } - void GetListOfDicomModalities(std::set<std::string>& target) + void Configuration::GetListOfDicomModalities(std::set<std::string>& target) { if (!ReadKeys(target, "DicomModalities", true)) { @@ -412,7 +384,7 @@ } - void GetListOfOrthancPeers(std::set<std::string>& target) + void Configuration::GetListOfOrthancPeers(std::set<std::string>& target) { if (!ReadKeys(target, "OrthancPeers", true)) { @@ -422,7 +394,7 @@ - void SetupRegisteredUsers(MongooseServer& httpServer) + void Configuration::SetupRegisteredUsers(MongooseServer& httpServer) { boost::mutex::scoped_lock lock(globalMutex_); @@ -449,8 +421,8 @@ } - std::string InterpretRelativePath(const std::string& baseDirectory, - const std::string& relativePath) + std::string Configuration::InterpretRelativePath(const std::string& baseDirectory, + const std::string& relativePath) { boost::filesystem::path base(baseDirectory); boost::filesystem::path relative(relativePath); @@ -475,15 +447,15 @@ } } - std::string InterpretStringParameterAsPath(const std::string& parameter) + std::string Configuration::InterpretStringParameterAsPath(const std::string& parameter) { boost::mutex::scoped_lock lock(globalMutex_); return InterpretRelativePath(defaultDirectory_.string(), parameter); } - void GetGlobalListOfStringsParameter(std::list<std::string>& target, - const std::string& key) + void Configuration::GetGlobalListOfStringsParameter(std::list<std::string>& target, + const std::string& key) { boost::mutex::scoped_lock lock(globalMutex_); @@ -508,27 +480,8 @@ } - void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, - const std::string& name) - { - std::string aet, address; - int port; - ModalityManufacturer manufacturer; - GetDicomModalityUsingSymbolicName(name, aet, address, port, manufacturer); - - LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; - - connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); - connection.SetDistantApplicationEntityTitle(aet); - connection.SetDistantHost(address); - connection.SetDistantPort(port); - connection.SetDistantManufacturer(manufacturer); - connection.Open(); - } - - - bool IsSameAETitle(const std::string& aet1, - const std::string& aet2) + bool Configuration::IsSameAETitle(const std::string& aet1, + const std::string& aet2) { if (GetGlobalBoolParameter("StrictAetComparison", false)) { @@ -546,11 +499,8 @@ } - bool LookupDicomModalityUsingAETitle(const std::string& aet, - std::string& symbolicName, - std::string& address, - int& port, - ModalityManufacturer& manufacturer) + bool Configuration::LookupDicomModalityUsingAETitle(RemoteModalityParameters& modality, + const std::string& aet) { std::set<std::string> modalities; GetListOfDicomModalities(modalities); @@ -560,10 +510,9 @@ { try { - std::string thisAet; - GetDicomModalityUsingSymbolicName(*it, thisAet, address, port, manufacturer); + GetDicomModalityUsingSymbolicName(modality, *it); - if (IsSameAETitle(aet, thisAet)) + if (IsSameAETitle(aet, modality.GetApplicationEntityTitle())) { return true; } @@ -577,35 +526,119 @@ } - bool IsKnownAETitle(const std::string& aet) + bool Configuration::IsKnownAETitle(const std::string& aet) + { + RemoteModalityParameters modality; + return LookupDicomModalityUsingAETitle(modality, aet); + } + + + RemoteModalityParameters Configuration::GetModalityUsingSymbolicName(const std::string& name) { - std::string symbolicName, address; - int port; - ModalityManufacturer manufacturer; - - return LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer); + RemoteModalityParameters modality; + GetDicomModalityUsingSymbolicName(modality, name); + + return modality; + } + + + RemoteModalityParameters Configuration::GetModalityUsingAet(const std::string& aet) + { + RemoteModalityParameters modality; + + if (LookupDicomModalityUsingAETitle(modality, aet)) + { + return modality; + } + else + { + throw OrthancException("Unknown modality for AET: " + aet); + } } - void ConnectToModalityUsingAETitle(DicomUserConnection& connection, - const std::string& aet) + void Configuration::UpdateModality(const std::string& symbolicName, + const RemoteModalityParameters& modality) { - std::string symbolicName, address; - int port; - ModalityManufacturer manufacturer; + boost::mutex::scoped_lock lock(globalMutex_); + + if (!configuration_->isMember("DicomModalities")) + { + (*configuration_) ["DicomModalities"] = Json::objectValue; + } + + Json::Value& modalities = (*configuration_) ["DicomModalities"]; + if (modalities.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + modalities.removeMember(symbolicName); - if (!LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer)) + Json::Value v; + modality.ToJson(v); + modalities[symbolicName] = v; + } + + + void Configuration::RemoveModality(const std::string& symbolicName) + { + boost::mutex::scoped_lock lock(globalMutex_); + + if (!configuration_->isMember("DicomModalities")) { - throw OrthancException("Unknown modality: " + aet); + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& modalities = (*configuration_) ["DicomModalities"]; + if (modalities.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); } - LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; + modalities.removeMember(symbolicName.c_str()); + } + + + void Configuration::UpdatePeer(const std::string& symbolicName, + const OrthancPeerParameters& peer) + { + boost::mutex::scoped_lock lock(globalMutex_); + + if (!configuration_->isMember("OrthancPeers")) + { + (*configuration_) ["OrthancPeers"] = Json::objectValue; + } + + Json::Value& peers = (*configuration_) ["OrthancPeers"]; + if (peers.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + peers.removeMember(symbolicName); - connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); - connection.SetDistantApplicationEntityTitle(aet); - connection.SetDistantHost(address); - connection.SetDistantPort(port); - connection.SetDistantManufacturer(manufacturer); - connection.Open(); + Json::Value v; + peer.ToJson(v); + peers[symbolicName] = v; + } + + + void Configuration::RemovePeer(const std::string& symbolicName) + { + boost::mutex::scoped_lock lock(globalMutex_); + + if (!configuration_->isMember("OrthancPeers")) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& peers = (*configuration_) ["OrthancPeers"]; + if (peers.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + peers.removeMember(symbolicName.c_str()); } }
--- a/OrthancServer/OrthancInitialization.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancInitialization.h Wed Jun 25 12:09:38 2014 +0200 @@ -37,8 +37,9 @@ #include <json/json.h> #include <stdint.h> #include "../Core/HttpServer/MongooseServer.h" -#include "DicomProtocol/DicomUserConnection.h" +#include "DicomProtocol/RemoteModalityParameters.h" #include "ServerEnumerations.h" +#include "OrthancPeerParameters.h" namespace Orthanc { @@ -46,54 +47,58 @@ void OrthancFinalize(); - std::string GetGlobalStringParameter(const std::string& parameter, - const std::string& defaultValue); + class Configuration + { + public: + static std::string GetGlobalStringParameter(const std::string& parameter, + const std::string& defaultValue); - int GetGlobalIntegerParameter(const std::string& parameter, - int defaultValue); + static int GetGlobalIntegerParameter(const std::string& parameter, + int defaultValue); - bool GetGlobalBoolParameter(const std::string& parameter, - bool defaultValue); + static bool GetGlobalBoolParameter(const std::string& parameter, + bool defaultValue); - void GetDicomModalityUsingSymbolicName(const std::string& name, - std::string& aet, - std::string& address, - int& port, - ModalityManufacturer& manufacturer); + static void GetDicomModalityUsingSymbolicName(RemoteModalityParameters& modality, + const std::string& name); + + static bool LookupDicomModalityUsingAETitle(RemoteModalityParameters& modality, + const std::string& aet); - bool LookupDicomModalityUsingAETitle(const std::string& aet, - std::string& symbolicName, - std::string& address, - int& port, - ModalityManufacturer& manufacturer); + static void GetOrthancPeer(OrthancPeerParameters& peer, + const std::string& name); - void GetOrthancPeer(const std::string& name, - std::string& url, - std::string& username, - std::string& password); + static void GetListOfDicomModalities(std::set<std::string>& target); + + static void GetListOfOrthancPeers(std::set<std::string>& target); + + static void SetupRegisteredUsers(MongooseServer& httpServer); - void GetListOfDicomModalities(std::set<std::string>& target); + static std::string InterpretRelativePath(const std::string& baseDirectory, + const std::string& relativePath); - void GetListOfOrthancPeers(std::set<std::string>& target); + static std::string InterpretStringParameterAsPath(const std::string& parameter); - void SetupRegisteredUsers(MongooseServer& httpServer); + static void GetGlobalListOfStringsParameter(std::list<std::string>& target, + const std::string& key); - std::string InterpretRelativePath(const std::string& baseDirectory, - const std::string& relativePath); + static bool IsKnownAETitle(const std::string& aet); - std::string InterpretStringParameterAsPath(const std::string& parameter); + static bool IsSameAETitle(const std::string& aet1, + const std::string& aet2); - void GetGlobalListOfStringsParameter(std::list<std::string>& target, - const std::string& key); + static RemoteModalityParameters GetModalityUsingSymbolicName(const std::string& name); - void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, - const std::string& name); + static RemoteModalityParameters GetModalityUsingAet(const std::string& aet); + + static void UpdateModality(const std::string& symbolicName, + const RemoteModalityParameters& modality); - void ConnectToModalityUsingAETitle(DicomUserConnection& connection, - const std::string& aet); + static void RemoveModality(const std::string& symbolicName); - bool IsKnownAETitle(const std::string& aet); + static void UpdatePeer(const std::string& symbolicName, + const OrthancPeerParameters& peer); - bool IsSameAETitle(const std::string& aet1, - const std::string& aet2); + static void RemovePeer(const std::string& symbolicName); + }; }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -29,11 +29,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + +#include "PrecompiledHeadersServer.h" #include "OrthancMoveRequestHandler.h" #include <glog/logging.h> -#include "DicomProtocol/DicomUserConnection.h" #include "OrthancInitialization.h" namespace Orthanc @@ -47,17 +48,17 @@ private: ServerContext& context_; std::vector<std::string> instances_; - DicomUserConnection connection_; size_t position_; + RemoteModalityParameters remote_; public: OrthancMoveRequestIterator(ServerContext& context, - const std::string& target, + const std::string& aet, const std::string& publicId) : context_(context), position_(0) { - LOG(INFO) << "Sending resource " << publicId << " to modality \"" << target << "\""; + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\""; std::list<std::string> tmp; context_.GetIndex().GetChildInstances(tmp, publicId); @@ -67,8 +68,8 @@ { instances_.push_back(*it); } - - ConnectToModalityUsingAETitle(connection_, target); + + remote_ = Configuration::GetModalityUsingAet(aet); } virtual unsigned int GetSubOperationCount() const @@ -87,7 +88,12 @@ std::string dicom; context_.ReadFile(dicom, id, FileContentType_Dicom); - connection_.Store(dicom); + + { + ReusableDicomUserConnection::Locker locker + (context_.GetReusableDicomUserConnection(), remote_); + locker.GetConnection().Store(dicom); + } return Status_Success; } @@ -121,10 +127,10 @@ } - IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& target, + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& aet, const DicomMap& input) { - LOG(WARNING) << "Move-SCU request received for AET \"" << target << "\""; + LOG(WARNING) << "Move-SCU request received for AET \"" << aet << "\""; /** @@ -173,6 +179,6 @@ throw OrthancException(ErrorCode_BadRequest); } - return new OrthancMoveRequestIterator(context_, target, publicId); + return new OrthancMoveRequestIterator(context_, aet, publicId); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancPeerParameters.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,96 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "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()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancPeerParameters.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,84 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include <json/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 Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,90 +30,59 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../DicomModification.h" +#include "../FromDcmtkBridge.h" + #include <glog/logging.h> namespace Orthanc { - // TODO IMPROVE MULTITHREADING - // Every call to "ParsedDicomFile" must lock this mutex!!! - static boost::mutex cacheMutex_; - - - // Raw access to the DICOM tags of an instance ------------------------------ - - static void GetRawContent(RestApi::GetCall& call) - { - boost::mutex::scoped_lock lock(cacheMutex_); - - ServerContext& context = OrthancRestApi::GetContext(call); - - std::string id = call.GetUriComponent("id", ""); - ParsedDicomFile& dicom = context.GetDicomFile(id); - dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); - } - - - // Modification of DICOM instances ------------------------------------------ - namespace + enum TagOperation { - typedef std::set<DicomTag> Removals; - typedef std::map<DicomTag, std::string> Replacements; - typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap; - } - - static void ReplaceInstanceInternal(ParsedDicomFile& toModify, - const Removals& removals, - const Replacements& replacements, - DicomReplaceMode mode, - bool removePrivateTags) - { - if (removePrivateTags) - { - toModify.RemovePrivateTags(); - } + TagOperation_Keep, + TagOperation_Remove + }; - for (Removals::const_iterator it = removals.begin(); - it != removals.end(); ++it) - { - toModify.Remove(*it); - } - - for (Replacements::const_iterator it = replacements.begin(); - it != replacements.end(); ++it) - { - toModify.Replace(it->first, it->second, mode); - } - - // A new SOP instance UID is automatically generated - std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); - toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent); - } - - - static void ParseRemovals(Removals& target, - const Json::Value& removals) + static void ParseListOfTags(DicomModification& target, + const Json::Value& query, + TagOperation operation) { - if (!removals.isArray()) + if (!query.isArray()) { throw OrthancException(ErrorCode_BadRequest); } - for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) + for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) { - std::string name = removals[i].asString(); + std::string name = query[i].asString(); + DicomTag tag = FromDcmtkBridge::ParseTag(name); - target.insert(tag); + + switch (operation) + { + case TagOperation_Keep: + target.Keep(tag); + VLOG(1) << "Keep: " << name << " " << tag << std::endl; + break; - VLOG(1) << "Removal: " << name << " " << tag << std::endl; + case TagOperation_Remove: + target.Remove(tag); + VLOG(1) << "Remove: " << name << " " << tag << std::endl; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } } } - static void ParseReplacements(Replacements& target, + static void ParseReplacements(DicomModification& target, const Json::Value& replacements) { if (!replacements.isObject()) @@ -127,10 +96,10 @@ const std::string& name = members[i]; std::string value = replacements[name].asString(); - DicomTag tag = FromDcmtkBridge::ParseTag(name); - target[tag] = value; + DicomTag tag = FromDcmtkBridge::ParseTag(name); + target.Replace(tag, value); - VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl; + VLOG(1) << "Replace: " << name << " " << tag << " == " << value << std::endl; } } @@ -142,186 +111,27 @@ } - static void SetupAnonymization(Removals& removals, - Replacements& replacements) - { - // 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 by ReplaceInstanceInternal() - 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 - //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 => cf. below (*) - //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => cf. below (*) - removals.insert(DicomTag(0x0020, 0x0010)); // Study ID - removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID - removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID - 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 - - /** - * (*) Patient ID, Study Instance UID and Series Instance UID - * are modified by "AnonymizeInstance()" if anonymizing a single - * instance, or by "RetrieveMappedUid()" if anonymizing a - * patient/study/series. - **/ - - - // Some more removals (from the experience of DICOM files at the CHU of Liege) - removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address - removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers - removals.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - - // Set the DeidentificationMethod tag - replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); - - // Set the PatientIdentityRemoved tag - replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); - } - - - static bool ParseModifyRequest(Removals& removals, - Replacements& replacements, - bool& removePrivateTags, + static bool ParseModifyRequest(DicomModification& target, const RestApi::PostCall& call) { - removePrivateTags = false; + // 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) && - request.isObject()) + if (call.ParseJsonRequest(request) && request.isObject()) { - Json::Value removalsPart = Json::arrayValue; - Json::Value replacementsPart = Json::objectValue; + if (request.isMember("RemovePrivateTags")) + { + target.SetRemovePrivateTags(true); + } if (request.isMember("Remove")) { - removalsPart = request["Remove"]; + ParseListOfTags(target, request["Remove"], TagOperation_Remove); } if (request.isMember("Replace")) { - replacementsPart = request["Replace"]; - } - - if (request.isMember("RemovePrivateTags")) - { - removePrivateTags = true; - } - - ParseRemovals(removals, removalsPart); - ParseReplacements(replacements, replacementsPart); - - return true; - } - else - { - return false; - } - } - - - static bool ParseAnonymizationRequest(Removals& removals, - Replacements& replacements, - bool& removePrivateTags, - bool& keepPatientId, - RestApi::PostCall& call) - { - ServerContext& context = OrthancRestApi::GetContext(call); - - removePrivateTags = true; - keepPatientId = false; - - Json::Value request; - if (call.ParseJsonRequest(request) && - request.isObject()) - { - Json::Value keepPart = Json::arrayValue; - Json::Value removalsPart = Json::arrayValue; - Json::Value replacementsPart = Json::objectValue; - - if (request.isMember("Keep")) - { - keepPart = request["Keep"]; - } - - if (request.isMember("KeepPrivateTags")) - { - removePrivateTags = false; - } - - if (request.isMember("Replace")) - { - replacementsPart = request["Replace"]; - } - - Removals toKeep; - ParseRemovals(toKeep, keepPart); - - SetupAnonymization(removals, replacements); - - for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); ++it) - { - if (*it == DICOM_TAG_PATIENT_ID) - { - keepPatientId = true; - } - - removals.erase(*it); - } - - Removals additionalRemovals; - ParseRemovals(additionalRemovals, removalsPart); - - for (Removals::iterator it = additionalRemovals.begin(); - it != additionalRemovals.end(); ++it) - { - removals.insert(*it); - } - - ParseReplacements(replacements, replacementsPart); - - // Generate random Patient's Name if none is specified - if (toKeep.find(DICOM_TAG_PATIENT_NAME) == toKeep.end() && - replacements.find(DICOM_TAG_PATIENT_NAME) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_PATIENT_NAME, GeneratePatientName(context))); + ParseReplacements(target, request["Replace"]); } return true; @@ -333,92 +143,78 @@ } - static void AnonymizeOrModifyInstance(Removals& removals, - Replacements& replacements, - bool removePrivateTags, + static bool ParseAnonymizationRequest(DicomModification& target, RestApi::PostCall& call) { - boost::mutex::scoped_lock lock(cacheMutex_); - ServerContext& context = OrthancRestApi::GetContext(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.GetReplacement(DICOM_TAG_PATIENT_NAME); + + Json::Value request; + if (call.ParseJsonRequest(request) && request.isObject()) + { + if (request.isMember("KeepPrivateTags")) + { + target.SetRemovePrivateTags(false); + } + + if (request.isMember("Remove")) + { + ParseListOfTags(target, request["Remove"], TagOperation_Remove); + } + + if (request.isMember("Replace")) + { + ParseReplacements(target, request["Replace"]); + } + if (request.isMember("Keep")) + { + ParseListOfTags(target, request["Keep"], TagOperation_Keep); + } + + if (target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) + { + // 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))); + } + + return true; + } + else + { + return false; + } + } + + + static void AnonymizeOrModifyInstance(DicomModification& modification, + RestApi::PostCall& call) + { std::string id = call.GetUriComponent("id", ""); - ParsedDicomFile& dicom = context.GetDicomFile(id); - - std::auto_ptr<ParsedDicomFile> modified(dicom.Clone()); - ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); + + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); + + std::auto_ptr<ParsedDicomFile> modified(locker.GetDicom().Clone()); + modification.Apply(*modified); modified->Answer(call.GetOutput()); } - static bool RetrieveMappedUid(ParsedDicomFile& dicom, - DicomRootLevel level, - Replacements& replacements, - UidMap& uidMap) - { - std::auto_ptr<DicomTag> tag; - - switch (level) - { - case DicomRootLevel_Series: - tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - break; - - case DicomRootLevel_Study: - tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - break; - - case DicomRootLevel_Patient: - tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID)); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - std::string original; - if (!dicom.GetTagValue(original, *tag)) - { - throw OrthancException(ErrorCode_InternalError); - } - - std::string mapped; - bool isNew; - - 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)); - isNew = true; - } - else - { - mapped = previous->second; - isNew = false; - } - - replacements[*tag] = mapped; - return isNew; - } - - - static void AnonymizeOrModifyResource(Removals& removals, - Replacements& replacements, - bool removePrivateTags, - bool keepPatientId, + static void AnonymizeOrModifyResource(DicomModification& modification, MetadataType metadataType, ChangeType changeType, ResourceType resourceType, RestApi::PostCall& call) { - typedef std::list<std::string> Instances; - bool isFirst = true; Json::Value result(Json::objectValue); - boost::mutex::scoped_lock lock(cacheMutex_); ServerContext& context = OrthancRestApi::GetContext(call); + typedef std::list<std::string> Instances; Instances instances; std::string id = call.GetUriComponent("id", ""); context.GetIndex().GetChildInstances(instances, id); @@ -428,28 +224,30 @@ return; } + /** * Loop over all the instances of the resource. **/ - UidMap uidMap; for (Instances::const_iterator it = instances.begin(); it != instances.end(); ++it) { LOG(INFO) << "Modifying instance " << *it; - ParsedDicomFile& original = context.GetDicomFile(*it); - DicomInstanceHasher originalHasher = original.GetHasher(); + std::auto_ptr<ServerContext::DicomCacheLocker> locker; - if (isFirst && keepPatientId) + try { - std::string patientId = originalHasher.GetPatientId(); - uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId; + locker.reset(new ServerContext::DicomCacheLocker(OrthancRestApi::GetContext(call), *it)); + } + catch (OrthancException&) + { + // This child instance has been removed in between + continue; } - bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); - bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); - bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap); + ParsedDicomFile& original = locker->GetDicom(); + DicomInstanceHasher originalHasher = original.GetHasher(); /** @@ -457,10 +255,10 @@ **/ std::auto_ptr<ParsedDicomFile> modified(original.Clone()); - ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); + modification.Apply(*modified); std::string modifiedInstance; - if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) + if (context.Store(modifiedInstance, *modified) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << *it; return; @@ -473,19 +271,19 @@ DicomInstanceHasher modifiedHasher = modified->GetHasher(); - if (isNewSeries) + if (originalHasher.HashSeries() != modifiedHasher.HashSeries()) { context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), metadataType, originalHasher.HashSeries()); } - if (isNewStudy) + if (originalHasher.HashStudy() != modifiedHasher.HashStudy()) { context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), metadataType, originalHasher.HashStudy()); } - if (isNewPatient) + if (originalHasher.HashPatient() != modifiedHasher.HashPatient()) { context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), metadataType, originalHasher.HashPatient()); @@ -537,156 +335,119 @@ static void ModifyInstance(RestApi::PostCall& call) { - Removals removals; - Replacements replacements; - bool removePrivateTags; + DicomModification modification; - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + if (ParseModifyRequest(modification, call)) { - AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); + 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); + } + + AnonymizeOrModifyInstance(modification, call); } } static void AnonymizeInstance(RestApi::PostCall& call) { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) - { - // TODO Handle "keepPatientId" - - // Generate random patient ID if not specified - if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); - } + DicomModification modification; - // Generate random study UID if not specified - if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); - } - - // Generate random series UID if not specified - if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); - } - - AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); + if (ParseAnonymizationRequest(modification, call)) + { + AnonymizeOrModifyInstance(modification, call); } } - static void ModifySeriesInplace(RestApi::PostCall& call) + template <enum ChangeType changeType, + enum ResourceType resourceType> + static void ModifyResource(RestApi::PostCall& call) { - Removals removals; - Replacements replacements; - bool removePrivateTags; + DicomModification modification; - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + if (ParseModifyRequest(modification, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, - MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, - ResourceType_Series, call); + modification.SetLevel(resourceType); + AnonymizeOrModifyResource(modification, MetadataType_ModifiedFrom, + changeType, resourceType, call); } } - static void AnonymizeSeriesInplace(RestApi::PostCall& call) + template <enum ChangeType changeType, + enum ResourceType resourceType> + static void AnonymizeResource(RestApi::PostCall& call) { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; + DicomModification modification; - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + if (ParseAnonymizationRequest(modification, call)) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, - ResourceType_Series, call); + AnonymizeOrModifyResource(modification, MetadataType_AnonymizedFrom, + changeType, resourceType, call); } } - static void ModifyStudyInplace(RestApi::PostCall& call) + static void Create(RestApi::PostCall& call) { - Removals removals; - Replacements replacements; - bool removePrivateTags; + // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}' + // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}' - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + Json::Value request; + if (call.ParseJsonRequest(request) && request.isObject()) { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, - MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, - ResourceType_Study, call); - } - } + DicomModification modification; + ParseReplacements(modification, request); + + ParsedDicomFile dicom; + if (modification.IsReplaced(DICOM_TAG_PIXEL_DATA)) + { + dicom.EmbedImage(modification.GetReplacement(DICOM_TAG_PIXEL_DATA)); + modification.Keep(DICOM_TAG_PIXEL_DATA); + } - static void AnonymizeStudyInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; + modification.Apply(dicom); + + std::string id; + StoreStatus status = OrthancRestApi::GetContext(call).Store(id, dicom); - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, - ResourceType_Study, call); + if (status == StoreStatus_Failure) + { + LOG(ERROR) << "Error while storing a manually-created instance"; + return; + } + + OrthancRestApi::GetApi(call).AnswerStoredInstance(call, id, status); } } - /*static void ModifyPatientInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, - MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, - ResourceType_Patient, call); - } - }*/ - - - static void AnonymizePatientInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, - ResourceType_Patient, call); - } - } - - - void OrthancRestApi::RegisterAnonymizeModify() { - Register("/instances/{id}/content/*", GetRawContent); - Register("/instances/{id}/modify", ModifyInstance); - Register("/series/{id}/modify", ModifySeriesInplace); - Register("/studies/{id}/modify", ModifyStudyInplace); - //Register("/patients/{id}/modify", ModifyPatientInplace); + 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("/instances/{id}/anonymize", AnonymizeInstance); - Register("/series/{id}/anonymize", AnonymizeSeriesInplace); - Register("/studies/{id}/anonymize", AnonymizeStudyInplace); - Register("/patients/{id}/anonymize", AnonymizePatientInplace); + 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("/tools/create-dicom", Create); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,12 +30,33 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../DicomModification.h" + #include <glog/logging.h> namespace Orthanc { + void OrthancRestApi::AnswerStoredInstance(RestApi::PostCall& call, + const std::string& publicId, + StoreStatus status) + { + Json::Value result = Json::objectValue; + + if (status != StoreStatus_Failure) + { + result["ID"] = publicId; + result["Path"] = GetBasePath(ResourceType_Instance, publicId); + } + + result["Status"] = EnumerationToString(status); + call.GetOutput().AnswerJson(result); + } + + + // Upload of DICOM files through HTTP --------------------------------------- static void UploadDicomFile(RestApi::PostCall& call) @@ -52,16 +73,8 @@ std::string publicId; StoreStatus status = context.Store(publicId, postData); - Json::Value result = Json::objectValue; - if (status != StoreStatus_Failure) - { - result["ID"] = publicId; - result["Path"] = GetBasePath(ResourceType_Instance, publicId); - } - - result["Status"] = EnumerationToString(status); - call.GetOutput().AnswerJson(result); + OrthancRestApi::GetApi(call).AnswerStoredInstance(call, publicId, status); }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Jun 25 12:09:38 2014 +0200 @@ -62,15 +62,23 @@ public: OrthancRestApi(ServerContext& context); + static OrthancRestApi& GetApi(RestApi::Call& call) + { + return dynamic_cast<OrthancRestApi&>(call.GetContext()); + } + static ServerContext& GetContext(RestApi::Call& call) { - OrthancRestApi& that = dynamic_cast<OrthancRestApi&>(call.GetContext()); - return that.context_; + return GetApi(call).context_; } static ServerIndex& GetIndex(RestApi::Call& call) { return GetContext(call).GetIndex(); } + + void AnswerStoredInstance(RestApi::PostCall& call, + const std::string& publicId, + StoreStatus status); }; }
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" #include "../../Core/Compression/HierarchicalZipWriter.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" #include <glog/logging.h>
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,11 +30,12 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include "../DicomProtocol/DicomUserConnection.h" #include "../OrthancInitialization.h" #include "../../Core/HttpClient.h" +#include "../FromDcmtkBridge.h" #include <glog/logging.h> @@ -66,6 +67,8 @@ static void DicomFindPatient(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindPatientTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -73,11 +76,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); DicomFindAnswers answers; - connection.FindPatient(answers, m); + locker.GetConnection().FindPatient(answers, m); Json::Value result; answers.ToJson(result); @@ -86,6 +89,8 @@ static void DicomFindStudy(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindStudyTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -99,11 +104,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers answers; - connection.FindStudy(answers, m); + locker.GetConnection().FindStudy(answers, m); Json::Value result; answers.ToJson(result); @@ -112,6 +117,8 @@ static void DicomFindSeries(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindSeriesTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -126,11 +133,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers answers; - connection.FindSeries(answers, m); + locker.GetConnection().FindSeries(answers, m); Json::Value result; answers.ToJson(result); @@ -139,6 +146,8 @@ static void DicomFindInstance(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindInstanceTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -154,11 +163,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers answers; - connection.FindInstance(answers, m); + locker.GetConnection().FindInstance(answers, m); Json::Value result; answers.ToJson(result); @@ -167,6 +176,8 @@ static void DicomFind(RestApi::PostCall& call) { + ServerContext& context = OrthancRestApi::GetContext(call); + DicomMap m; DicomMap::SetupFindPatientTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -174,11 +185,11 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - + RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + DicomFindAnswers patients; - connection.FindPatient(patients, m); + locker.GetConnection().FindPatient(patients, m); // Loop over the found patients Json::Value result = Json::arrayValue; @@ -195,7 +206,7 @@ m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); DicomFindAnswers studies; - connection.FindStudy(studies, m); + locker.GetConnection().FindStudy(studies, m); patient["Studies"] = Json::arrayValue; @@ -214,7 +225,7 @@ m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); DicomFindAnswers series; - connection.FindSeries(series, m); + locker.GetConnection().FindSeries(series, m); // Loop over the found series study["Series"] = Json::arrayValue; @@ -309,8 +320,8 @@ return; } - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, remote); + RemoteModalityParameters p = Configuration::GetModalityUsingSymbolicName(remote); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), p); for (std::list<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it) @@ -319,7 +330,7 @@ std::string dicom; context.ReadFile(dicom, *it, FileContentType_Dicom); - connection.Store(dicom); + locker.GetConnection().Store(dicom); } call.GetOutput().AnswerBuffer("{}", "application/json"); @@ -337,7 +348,7 @@ static void ListPeers(RestApi::GetCall& call) { OrthancRestApi::SetOfStrings peers; - GetListOfOrthancPeers(peers); + Configuration::GetListOfOrthancPeers(peers); Json::Value result = Json::arrayValue; for (OrthancRestApi::SetOfStrings::const_iterator @@ -352,7 +363,7 @@ static void ListPeerOperations(RestApi::GetCall& call) { OrthancRestApi::SetOfStrings peers; - GetListOfOrthancPeers(peers); + Configuration::GetListOfOrthancPeers(peers); std::string id = call.GetUriComponent("id", ""); if (IsExistingPeer(peers, id)) @@ -375,17 +386,19 @@ return; } - std::string url, username, password; - GetOrthancPeer(remote, url, username, password); + OrthancPeerParameters peer; + Configuration::GetOrthancPeer(peer, remote); // Configure the HTTP client HttpClient client; - if (username.size() != 0 && password.size() != 0) + if (peer.GetUsername().size() != 0 && + peer.GetPassword().size() != 0) { - client.SetCredentials(username.c_str(), password.c_str()); + client.SetCredentials(peer.GetUsername().c_str(), + peer.GetPassword().c_str()); } - client.SetUrl(url + "instances"); + client.SetUrl(peer.GetUrl() + "instances"); client.SetMethod(HttpMethod_Post); // Loop over the instances that are to be sent @@ -419,7 +432,7 @@ static void ListModalities(RestApi::GetCall& call) { OrthancRestApi::SetOfStrings modalities; - GetListOfDicomModalities(modalities); + Configuration::GetListOfDicomModalities(modalities); Json::Value result = Json::arrayValue; for (OrthancRestApi::SetOfStrings::const_iterator @@ -435,7 +448,7 @@ static void ListModalityOperations(RestApi::GetCall& call) { OrthancRestApi::SetOfStrings modalities; - GetListOfDicomModalities(modalities); + Configuration::GetListOfDicomModalities(modalities); std::string id = call.GetUriComponent("id", ""); if (IsExistingModality(modalities, id)) @@ -452,10 +465,54 @@ } + static void UpdateModality(RestApi::PutCall& call) + { + Json::Value json; + Json::Reader reader; + if (reader.parse(call.GetPutBody(), json)) + { + RemoteModalityParameters modality; + modality.FromJson(json); + Configuration::UpdateModality(call.GetUriComponent("id", ""), modality); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + static void DeleteModality(RestApi::DeleteCall& call) + { + Configuration::RemoveModality(call.GetUriComponent("id", "")); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + static void UpdatePeer(RestApi::PutCall& call) + { + Json::Value json; + Json::Reader reader; + if (reader.parse(call.GetPutBody(), json)) + { + OrthancPeerParameters peer; + peer.FromJson(json); + Configuration::UpdatePeer(call.GetUriComponent("id", ""), peer); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + static void DeletePeer(RestApi::DeleteCall& call) + { + Configuration::RemovePeer(call.GetUriComponent("id", "")); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + void OrthancRestApi::RegisterModalities() { Register("/modalities", ListModalities); Register("/modalities/{id}", ListModalityOperations); + Register("/modalities/{id}", UpdateModality); + Register("/modalities/{id}", DeleteModality); Register("/modalities/{id}/find-patient", DicomFindPatient); Register("/modalities/{id}/find-study", DicomFindStudy); Register("/modalities/{id}/find-series", DicomFindSeries); @@ -465,6 +522,8 @@ Register("/peers", ListPeers); Register("/peers/{id}", ListPeerOperations); + Register("/peers/{id}", UpdatePeer); + Register("/peers/{id}", DeletePeer); Register("/peers/{id}/store", PeerStore); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,9 +30,11 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" #include "../ServerToolbox.h" +#include "../FromDcmtkBridge.h" #include <glog/logging.h> @@ -200,9 +202,11 @@ std::string dicomContent, png; context.ReadFile(dicomContent, publicId, FileContentType_Dicom); + ParsedDicomFile dicom(dicomContent); + try { - FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); + dicom.ExtractPngImage(png, frame, mode); call.GetOutput().AnswerBuffer(png, "image/png"); } catch (OrthancException& e) @@ -226,6 +230,39 @@ } + static void GetMatlabImage(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(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 dicomContent; + context.ReadFile(dicomContent, publicId, FileContentType_Dicom); + + ParsedDicomFile dicom(dicomContent); + ImageBuffer buffer; + dicom.ExtractImage(buffer, frame); + + ImageAccessor accessor(buffer.GetConstAccessor()); + + std::string result; + accessor.ToMatlabString(result); + + call.GetOutput().AnswerBuffer(result, "text/plain"); + } + + static void GetResourceStatistics(RestApi::GetCall& call) { @@ -541,6 +578,17 @@ } + // Raw access to the DICOM tags of an instance ------------------------------ + + static void GetRawContent(RestApi::GetCall& call) + { + std::string id = call.GetUriComponent("id", ""); + + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); + + locker.GetDicom().SendPathValue(call.GetOutput(), call.GetTrailingUri()); + } + void OrthancRestApi::RegisterResources() @@ -574,10 +622,12 @@ Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); + Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); 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("/patients/{id}/protected", IsProtectedPatient); Register("/patients/{id}/protected", SetPatientProtection); @@ -598,5 +648,7 @@ Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); + + Register("/instances/{id}/content/*", GetRawContent); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,9 +30,11 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" #include "../OrthancInitialization.h" +#include "../FromDcmtkBridge.h" #include <glog/logging.h> @@ -51,7 +53,7 @@ Json::Value result = Json::objectValue; result["Version"] = ORTHANC_VERSION; - result["Name"] = GetGlobalStringParameter("Name", ""); + result["Name"] = Configuration::GetGlobalStringParameter("Name", ""); call.GetOutput().AnswerJson(result); } @@ -68,19 +70,19 @@ std::string level = call.GetArgument("level", ""); if (level == "patient") { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient), "text/plain"); + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient), "text/plain"); } else if (level == "study") { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study), "text/plain"); + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study), "text/plain"); } else if (level == "series") { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series), "text/plain"); + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series), "text/plain"); } else if (level == "instance") { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance), "text/plain"); + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance), "text/plain"); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ParsedDicomFile.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,1282 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + +/*========================================================================= + + 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 "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" +#include "Internals/DicomImageDecoder.h" +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Uuid.h" +#include "../Core/DicomFormat/DicomString.h" +#include "../Core/DicomFormat/DicomNullValue.h" +#include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" +#include "../Core/ImageFormats/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/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 <glog/logging.h> +#include <dcmtk/dcmdata/dcostrmb.h> + + +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; + } + } + + + static void AnswerDicomField(RestApiOutput& output, + DcmElement& element, + E_TransferSyntax transferSyntax) + { + // This element is nor a sequence, neither a pixel-data + std::string buffer; + buffer.resize(65536); + Uint32 length = element.getLength(transferSyntax); + Uint32 offset = 0; + + output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL); + + while (offset < length) + { + Uint32 nbytes; + if (length - offset < buffer.size()) + { + nbytes = length - offset; + } + else + { + nbytes = buffer.size(); + } + + OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes); + + if (cond.good()) + { + output.GetLowLevelOutput().Send(&buffer[0], nbytes); + offset += nbytes; + } + else + { + LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); + return; + } + } + + output.MarkLowLevelOutputDone(); + } + + + 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"); + AnswerDicomField(output, *element, transferSyntax); + } + } + } + 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) + { + AnswerDicomField(output, *element, transferSyntax); + } + } + + 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); + } + } + + + + + + static DcmElement* CreateElementForTag(const DicomTag& tag) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + + 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. + * 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); + + + /** + * 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; + } + + throw OrthancException(ErrorCode_InternalError); + } + + + + static void FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& value) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + 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(value.c_str()).good(); + break; + } + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + { + ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good(); + break; + } + + case EVR_SS: // signed short + { + ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good(); + break; + } + + case EVR_UL: // unsigned long + { + ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good(); + break; + } + + case EVR_US: // unsigned short + { + ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good(); + break; + } + + case EVR_FL: // float single-precision + { + ok = element.putFloat32(boost::lexical_cast<float>(value)).good(); + break; + } + + case EVR_FD: // float double-precision + { + ok = element.putFloat64(boost::lexical_cast<double>(value)).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); + } + } + + + 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::RemovePrivateTags() + { + typedef std::list<DcmElement*> Tags; + + Tags privateTags; + + DcmDataset& dataset = *pimpl_->file_->getDataset(); + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + DcmTag tag(element->getTag()); + if (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK + tag.getPrivateCreator() != NULL) + { + privateTags.push_back(element); + } + } + + for (Tags::iterator it = privateTags.begin(); + it != privateTags.end(); ++it) + { + DcmElement* tmp = dataset.remove(*it); + if (tmp != NULL) + { + delete tmp; + } + } + } + + + + void ParsedDicomFile::Insert(const DicomTag& tag, + const std::string& value) + { + std::auto_ptr<DcmElement> element(CreateElementForTag(tag)); + FillElementWithString(*element, tag, value); + + if (!pimpl_->file_->getDataset()->insert(element.release(), false, false).good()) + { + // This field already exists + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& value, + DicomReplaceMode mode) + { + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = NULL; + + if (!pimpl_->file_->getDataset()->findAndGetElement(key, element).good() || + element == NULL) + { + // This field does not exist, act wrt. the specified "mode" + switch (mode) + { + case DicomReplaceMode_InsertIfAbsent: + Insert(tag, value); + break; + + case DicomReplaceMode_ThrowIfAbsent: + throw OrthancException(ErrorCode_InexistentItem); + + case DicomReplaceMode_IgnoreIfAbsent: + return; + } + } + else + { + FillElementWithString(*element, tag, value); + } + + + /** + * 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, value, DicomReplaceMode_InsertIfAbsent); + } + + if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent); + } + } + + + 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(); + DcmElement* element = NULL; + if (!dataset.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element)); + + if (v.get() == NULL) + { + value = ""; + } + else + { + value = v->AsString(); + } + + 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); + } + + + static void StoreElement(Json::Value& target, + DcmElement& element, + unsigned int maxStringLength); + + static void StoreItem(Json::Value& target, + DcmItem& item, + unsigned int maxStringLength) + { + target = Json::Value(Json::objectValue); + + for (unsigned long i = 0; i < item.card(); i++) + { + DcmElement* element = item.getElement(i); + StoreElement(target, *element, maxStringLength); + } + } + + + static void StoreElement(Json::Value& target, + DcmElement& element, + unsigned int maxStringLength) + { + assert(target.type() == Json::objectValue); + + DicomTag tag(FromDcmtkBridge::GetTag(element)); + const std::string formattedTag = tag.Format(); + +#if 0 + const std::string tagName = FromDcmtkBridge::GetName(tag); +#else + // This version of the code gives access to the name of the private tags + DcmTag tagbis(element.getTag()); + const std::string tagName(tagbis.getTagName()); +#endif + + if (element.isLeaf()) + { + Json::Value value(Json::objectValue); + value["Name"] = tagName; + + if (tagbis.getPrivateCreator() != NULL) + { + value["PrivateCreator"] = tagbis.getPrivateCreator(); + } + + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element)); + if (v->IsNull()) + { + value["Type"] = "Null"; + value["Value"] = Json::nullValue; + } + else + { + std::string s = v->AsString(); + if (maxStringLength == 0 || + s.size() <= maxStringLength) + { + value["Type"] = "String"; + value["Value"] = s; + } + else + { + value["Type"] = "TooLong"; + value["Value"] = Json::nullValue; + } + } + + target[formattedTag] = value; + } + else + { + Json::Value children(Json::arrayValue); + + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following 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 = children.append(Json::objectValue); + StoreItem(v, *child, maxStringLength); + } + + target[formattedTag]["Name"] = tagName; + target[formattedTag]["Type"] = "Sequence"; + target[formattedTag]["Value"] = children; + } + } + + + 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())); + } + + + ParsedDicomFile::~ParsedDicomFile() + { + delete pimpl_; + } + + + void* ParsedDicomFile::GetDcmtkObject() + { + return pimpl_->file_.get(); + } + + + ParsedDicomFile* ParsedDicomFile::Clone() + { + return new ParsedDicomFile(*this); + } + + + void ParsedDicomFile::EmbedImage(const std::string& dataUriScheme) + { + std::string mime, content; + Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme); + + std::string decoded; + Toolbox::DecodeBase64(decoded, content); + + if (mime == "image/png") + { + PngReader reader; + reader.ReadFromMemory(decoded); + 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); + break; + + case ImageExtractionMode_UInt16: + ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16); + break; + + case ImageExtractionMode_Int16: + ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16); + 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); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ParsedDicomFile.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,109 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/DicomFormat/DicomInstanceHasher.h" +#include "../Core/RestApi/RestApiOutput.h" +#include "ServerEnumerations.h" +#include "../Core/ImageFormats/ImageAccessor.h" +#include "../Core/ImageFormats/ImageBuffer.h" + +namespace Orthanc +{ + class ParsedDicomFile : public IDynamicObject + { + private: + struct PImpl; + PImpl* pimpl_; + + ParsedDicomFile(ParsedDicomFile& other); + + void Setup(const char* content, + size_t size); + + 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 Insert(const DicomTag& tag, + const std::string& value); + + void Replace(const DicomTag& tag, + const std::string& value, + DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); + + void RemovePrivateTags(); + + bool GetTagValue(std::string& value, + const DicomTag& tag); + + DicomInstanceHasher GetHasher(); + + void SaveToMemoryBuffer(std::string& buffer); + + void SaveToFile(const std::string& path); + + void EmbedImage(const ImageAccessor& accessor); + + void EmbedImage(const std::string& dataUriScheme); + + 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); + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/PrecompiledHeadersServer.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,33 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersServer.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/PrecompiledHeadersServer.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Core/PrecompiledHeaders.h" + +#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> + +#endif
--- a/OrthancServer/ServerContext.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/ServerContext.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,14 +30,18 @@ **/ +#include "PrecompiledHeadersServer.h" #include "ServerContext.h" #include "../Core/HttpServer/FilesystemHttpSender.h" #include "../Core/Lua/LuaFunctionCall.h" +#include "FromDcmtkBridge.h" #include "ServerToolbox.h" +#include "OrthancInitialization.h" #include <glog/logging.h> #include <EmbeddedResources.h> +#include <dcmtk/dcmdata/dcfilefo.h> #define ENABLE_DICOM_CACHE 1 @@ -65,6 +69,9 @@ provider_(*this), dicomCache_(provider_, DICOM_CACHE_SIZE) { + scu_.SetLocalApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")); + //scu_.SetMillisecondsBeforeClose(1); // The connection is always released + lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); } @@ -218,25 +225,43 @@ } - ParsedDicomFile& ServerContext::GetDicomFile(const std::string& instancePublicId) + ServerContext::DicomCacheLocker::DicomCacheLocker(ServerContext& that, + const std::string& instancePublicId) : + that_(that) { #if ENABLE_DICOM_CACHE == 0 static std::auto_ptr<IDynamicObject> p; p.reset(provider_.Provide(instancePublicId)); - return dynamic_cast<ParsedDicomFile&>(*p); + dicom_ = dynamic_cast<ParsedDicomFile*>(p.get()); #else - return dynamic_cast<ParsedDicomFile&>(dicomCache_.Access(instancePublicId)); + that_.dicomCacheMutex_.lock(); + dicom_ = &dynamic_cast<ParsedDicomFile&>(that_.dicomCache_.Access(instancePublicId)); #endif } + ServerContext::DicomCacheLocker::~DicomCacheLocker() + { +#if ENABLE_DICOM_CACHE == 0 +#else + that_.dicomCacheMutex_.unlock(); +#endif + } + + + static DcmFileFormat& GetDicom(ParsedDicomFile& file) + { + return *reinterpret_cast<DcmFileFormat*>(file.GetDcmtkObject()); + } + + StoreStatus ServerContext::Store(std::string& resultPublicId, - DcmFileFormat& dicomInstance, + ParsedDicomFile& dicomInstance, const char* dicomBuffer, size_t dicomSize) { DicomMap dicomSummary; - FromDcmtkBridge::Convert(dicomSummary, *dicomInstance.getDataset()); + FromDcmtkBridge::Convert(dicomSummary, *GetDicom(dicomInstance).getDataset()); try { @@ -244,7 +269,7 @@ resultPublicId = hasher.HashInstance(); Json::Value dicomJson; - FromDcmtkBridge::ToJson(dicomJson, *dicomInstance.getDataset()); + FromDcmtkBridge::ToJson(dicomJson, *GetDicom(dicomInstance).getDataset()); StoreStatus status = StoreStatus_Failure; if (dicomSize > 0) @@ -261,16 +286,16 @@ LogMissingRequiredTag(dicomSummary); } - throw e; + throw; } } StoreStatus ServerContext::Store(std::string& resultPublicId, - DcmFileFormat& dicomInstance) + ParsedDicomFile& dicomInstance) { std::string buffer; - if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, dicomInstance.getDataset())) + if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, GetDicom(dicomInstance).getDataset())) { throw OrthancException(ErrorCode_InternalError); } @@ -287,9 +312,24 @@ size_t dicomSize) { ParsedDicomFile dicom(dicomBuffer, dicomSize); - return Store(resultPublicId, dicom.GetDicom(), dicomBuffer, dicomSize); + return Store(resultPublicId, dicom, dicomBuffer, dicomSize); } + + StoreStatus ServerContext::Store(std::string& resultPublicId, + const std::string& dicomContent) + { + if (dicomContent.size() == 0) + { + return Store(resultPublicId, NULL, 0); + } + else + { + return Store(resultPublicId, &dicomContent[0], dicomContent.size()); + } + } + + void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) { LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no");
--- a/OrthancServer/ServerContext.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/ServerContext.h Wed Jun 25 12:09:38 2014 +0200 @@ -38,7 +38,8 @@ #include "../Core/RestApi/RestApiOutput.h" #include "../Core/Lua/LuaContext.h" #include "ServerIndex.h" -#include "FromDcmtkBridge.h" +#include "ParsedDicomFile.h" +#include "DicomProtocol/ReusableDicomUserConnection.h" namespace Orthanc { @@ -69,11 +70,31 @@ bool compressionEnabled_; DicomCacheProvider provider_; + boost::mutex dicomCacheMutex_; MemoryCache dicomCache_; + ReusableDicomUserConnection scu_; LuaContext lua_; public: + class DicomCacheLocker + { + private: + ServerContext& that_; + ParsedDicomFile *dicom_; + + public: + DicomCacheLocker(ServerContext& that, + const std::string& instancePublicId); + + ~DicomCacheLocker(); + + ParsedDicomFile& GetDicom() + { + return *dicom_; + } + }; + ServerContext(const boost::filesystem::path& storagePath, const boost::filesystem::path& indexPath); @@ -103,25 +124,19 @@ const std::string& remoteAet); StoreStatus Store(std::string& resultPublicId, - DcmFileFormat& dicomInstance, + ParsedDicomFile& dicomInstance, const char* dicomBuffer, size_t dicomSize); StoreStatus Store(std::string& resultPublicId, - DcmFileFormat& dicomInstance); + ParsedDicomFile& dicomInstance); StoreStatus Store(std::string& resultPublicId, const char* dicomBuffer, size_t dicomSize); StoreStatus Store(std::string& resultPublicId, - const std::string& dicomContent) - { - if (dicomContent.size() == 0) - return Store(resultPublicId, NULL, 0); - else - return Store(resultPublicId, &dicomContent[0], dicomContent.size()); - } + const std::string& dicomContent); void AnswerDicomFile(RestApiOutput& output, const std::string& instancePublicId, @@ -136,9 +151,6 @@ FileContentType content, bool uncompressIfNeeded = true); - // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD - ParsedDicomFile& GetDicomFile(const std::string& instancePublicId); - LuaContext& GetLuaContext() { return lua_; @@ -150,5 +162,10 @@ { return accessor_.IsStoreMD5(); } + + ReusableDicomUserConnection& GetReusableDicomUserConnection() + { + return scu_; + } }; }
--- a/OrthancServer/ServerEnumerations.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -29,6 +29,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + +#include "PrecompiledHeadersServer.h" #include "ServerEnumerations.h" #include "../Core/OrthancException.h"
--- a/OrthancServer/ServerEnumerations.h Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/ServerEnumerations.h Wed Jun 25 12:09:38 2014 +0200 @@ -70,6 +70,13 @@ DicomRequestType_Store }; + enum DicomReplaceMode + { + DicomReplaceMode_InsertIfAbsent, + DicomReplaceMode_ThrowIfAbsent, + DicomReplaceMode_IgnoreIfAbsent + }; + /** * WARNING: Do not change the explicit values in the enumerations
--- a/OrthancServer/ServerIndex.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/ServerIndex.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeadersServer.h" #include "ServerIndex.h" #ifndef NOMINMAX @@ -1497,7 +1498,7 @@ void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that) { - int stableAge = GetGlobalIntegerParameter("StableAge", 60); + int stableAge = Configuration::GetGlobalIntegerParameter("StableAge", 60); if (stableAge <= 0) { stableAge = 60;
--- a/OrthancServer/ServerToolbox.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/ServerToolbox.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeadersServer.h" #include "ServerToolbox.h" #include "../Core/OrthancException.h"
--- a/OrthancServer/ToDcmtkBridge.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/ToDcmtkBridge.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeadersServer.h" #include "ToDcmtkBridge.h" #include <memory>
--- a/OrthancServer/main.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/OrthancServer/main.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeadersServer.h" #include "OrthancRestApi/OrthancRestApi.h" #include <fstream> @@ -41,7 +42,7 @@ #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" #include "DicomProtocol/DicomServer.h" -#include "DicomProtocol/DicomUserConnection.h" +#include "DicomProtocol/ReusableDicomUserConnection.h" #include "OrthancInitialization.h" #include "ServerContext.h" #include "OrthancFindRequestHandler.h" @@ -97,7 +98,32 @@ virtual IFindRequestHandler* ConstructFindRequestHandler() { - return new OrthancFindRequestHandler(context_); + std::auto_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_)); + + result->SetMaxResults(Configuration::GetGlobalIntegerParameter("LimitFindResults", 0)); + result->SetMaxInstances(Configuration::GetGlobalIntegerParameter("LimitFindInstances", 0)); + + if (result->GetMaxResults() == 0) + { + LOG(INFO) << "No limit on the number of C-FIND results at the Patient, Study and Series levels"; + } + else + { + LOG(INFO) << "Maximum " << result->GetMaxResults() + << " results for C-FIND queries at the Patient, Study and Series levels"; + } + + if (result->GetMaxInstances() == 0) + { + LOG(INFO) << "No limit on the number of C-FIND results at the Instance level"; + } + else + { + LOG(INFO) << "Maximum " << result->GetMaxInstances() + << " instances will be returned for C-FIND queries at the Instance level"; + } + + return result.release(); } virtual IMoveRequestHandler* ConstructMoveRequestHandler() @@ -130,7 +156,7 @@ return true; } - if (!IsKnownAETitle(callingAet)) + if (!Configuration::IsKnownAETitle(callingAet)) { LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << callingAet << "\""; return false; @@ -324,24 +350,24 @@ OrthancInitialize(); } - std::string storageDirectoryStr = GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); - boost::filesystem::path storageDirectory = InterpretStringParameterAsPath(storageDirectoryStr); - boost::filesystem::path indexDirectory = - InterpretStringParameterAsPath(GetGlobalStringParameter("IndexDirectory", storageDirectoryStr)); + std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage"); + boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr); + boost::filesystem::path indexDirectory = Configuration::InterpretStringParameterAsPath( + Configuration::GetGlobalStringParameter("IndexDirectory", storageDirectoryStr)); ServerContext context(storageDirectory, indexDirectory); LOG(WARNING) << "Storage directory: " << storageDirectory; LOG(WARNING) << "Index directory: " << indexDirectory; - context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false)); - context.SetStoreMD5ForAttachments(GetGlobalBoolParameter("StoreMD5ForAttachments", true)); + context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false)); + context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true)); std::list<std::string> luaScripts; - GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); + Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); for (std::list<std::string>::const_iterator it = luaScripts.begin(); it != luaScripts.end(); ++it) { - std::string path = InterpretStringParameterAsPath(*it); + std::string path = Configuration::InterpretStringParameterAsPath(*it); LOG(WARNING) << "Installing the Lua scripts from: " << path; std::string script; Toolbox::ReadFile(script, path); @@ -351,7 +377,7 @@ try { - context.GetIndex().SetMaximumPatientCount(GetGlobalIntegerParameter("MaximumPatientCount", 0)); + context.GetIndex().SetMaximumPatientCount(Configuration::GetGlobalIntegerParameter("MaximumPatientCount", 0)); } catch (...) { @@ -360,7 +386,7 @@ try { - uint64_t size = GetGlobalIntegerParameter("MaximumStorageSize", 0); + uint64_t size = Configuration::GetGlobalIntegerParameter("MaximumStorageSize", 0); context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024); } catch (...) @@ -374,28 +400,28 @@ // DICOM server DicomServer dicomServer; OrthancApplicationEntityFilter dicomFilter; - dicomServer.SetCalledApplicationEntityTitleCheck(GetGlobalBoolParameter("DicomCheckCalledAet", false)); + dicomServer.SetCalledApplicationEntityTitleCheck(Configuration::GetGlobalBoolParameter("DicomCheckCalledAet", false)); dicomServer.SetStoreRequestHandlerFactory(serverFactory); dicomServer.SetMoveRequestHandlerFactory(serverFactory); dicomServer.SetFindRequestHandlerFactory(serverFactory); - dicomServer.SetPortNumber(GetGlobalIntegerParameter("DicomPort", 4242)); - dicomServer.SetApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + dicomServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("DicomPort", 4242)); + dicomServer.SetApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")); dicomServer.SetApplicationEntityFilter(dicomFilter); // HTTP server MyIncomingHttpRequestFilter httpFilter(context); MongooseServer httpServer; - httpServer.SetPortNumber(GetGlobalIntegerParameter("HttpPort", 8042)); - httpServer.SetRemoteAccessAllowed(GetGlobalBoolParameter("RemoteAccessAllowed", false)); + httpServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("HttpPort", 8042)); + httpServer.SetRemoteAccessAllowed(Configuration::GetGlobalBoolParameter("RemoteAccessAllowed", false)); httpServer.SetIncomingHttpRequestFilter(httpFilter); - httpServer.SetAuthenticationEnabled(GetGlobalBoolParameter("AuthenticationEnabled", false)); - SetupRegisteredUsers(httpServer); + httpServer.SetAuthenticationEnabled(Configuration::GetGlobalBoolParameter("AuthenticationEnabled", false)); + Configuration::SetupRegisteredUsers(httpServer); - if (GetGlobalBoolParameter("SslEnabled", false)) + if (Configuration::GetGlobalBoolParameter("SslEnabled", false)) { - std::string certificate = - InterpretStringParameterAsPath(GetGlobalStringParameter("SslCertificate", "certificate.pem")); + std::string certificate = Configuration::InterpretStringParameterAsPath( + Configuration::GetGlobalStringParameter("SslCertificate", "certificate.pem")); httpServer.SetSslEnabled(true); httpServer.SetSslCertificate(certificate.c_str()); } @@ -413,7 +439,7 @@ httpServer.RegisterHandler(new OrthancRestApi(context)); // GO !!! Start the requested servers - if (GetGlobalBoolParameter("HttpServerEnabled", true)) + if (Configuration::GetGlobalBoolParameter("HttpServerEnabled", true)) { httpServer.Start(); LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); @@ -423,7 +449,7 @@ LOG(WARNING) << "The HTTP server is disabled"; } - if (GetGlobalBoolParameter("DicomServerEnabled", true)) + if (Configuration::GetGlobalBoolParameter("DicomServerEnabled", true)) { dicomServer.Start(); LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber();
--- a/Resources/CMake/BoostConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/CMake/BoostConfiguration.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -8,7 +8,7 @@ #set(Boost_USE_STATIC_LIBS ON) find_package(Boost - COMPONENTS filesystem thread system date_time regex) + COMPONENTS filesystem thread system date_time regex locale) if (NOT Boost_FOUND) message(FATAL_ERROR "Unable to locate Boost on this system") @@ -53,7 +53,10 @@ ) set(BOOST_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" 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 @@ -76,10 +79,17 @@ add_definitions( -DBOOST_LOCALE_WITH_WCONV=1 ) + else() message(FATAL_ERROR "Support your platform here") endif() + if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp + ) + endif() + aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) list(APPEND BOOST_SOURCES @@ -120,6 +130,6 @@ source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) else() add_definitions( - -DBOOST_HAS_LOCALE=0 + -DBOOST_HAS_LOCALE=1 ) endif()
--- a/Resources/CMake/Compiler.cmake Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/CMake/Compiler.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -37,7 +37,8 @@ endif() -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") 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") @@ -69,6 +70,12 @@ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") endif() +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + add_definitions( + -D_XOPEN_SOURCE=1 + ) + link_libraries(iconv) + endif()
--- a/Resources/CMake/DcmtkConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -12,7 +12,6 @@ endif() - if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) SET(DCMTK_VERSION_NUMBER 360) SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) @@ -49,9 +48,47 @@ AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) + + if (ENABLE_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) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg16 DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmjpeg/include + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12 + ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg16 + ${DCMTK_SOURCES_DIR}/dcmimgle/include + ) + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/ddpiimpl.cc + ) + endif() + + + if (ENABLE_JPEG_LOSSLESS) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpls/libcharls DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmjpeg/include + ${DCMTK_SOURCES_DIR}/dcmjpls/include + ${DCMTK_SOURCES_DIR}/dcmjpls/libcharls + ) + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc + ) + list(APPEND DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc + ) + endif() + + # Source for the logging facility of DCMTK AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc
--- a/Resources/CMake/GoogleLogConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/CMake/GoogleLogConfiguration.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -28,7 +28,9 @@ set(ac_google_start_namespace "namespace google {") set(ac_google_end_namespace "}") - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" 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) @@ -83,13 +85,21 @@ ) endif() - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") # Install the specific configuration for LSB SDK configure_file( ${CMAKE_SOURCE_DIR}/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( + ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationDarwin.h + ${GOOGLE_LOG_SOURCES_DIR}/src/config.h + COPYONLY) else() configure_file( ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h @@ -130,13 +140,10 @@ # This is a patch for MinGW64 add_definitions(-D_TIME_H__S=1) endif() - endif() - - add_library(GoogleLog STATIC ${GOOGLE_LOG_SOURCES}) - link_libraries(GoogleLog) + set(STATIC_GOOGLE_LOG GoogleLog) else() CHECK_INCLUDE_FILE_CXX(glog/logging.h HAVE_GOOGLE_LOG_H)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/GoogleLogConfigurationDarwin.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,175 @@ +/* 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/LibCurlConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/CMake/LibCurlConfiguration.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -40,7 +40,9 @@ ) endif() - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") SET(TMP_OS "x86_64") else()
--- a/Resources/CMake/LuaConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/CMake/LuaConfiguration.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -51,7 +51,7 @@ ) add_library(Lua STATIC ${LUA_SOURCES}) - link_libraries(Lua) + set(STATIC_LUA Lua) source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*)
--- a/Resources/CMake/MongooseConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/CMake/MongooseConfiguration.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -24,7 +24,8 @@ add_definitions( -DNO_SSL_DL=1 ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") link_libraries(dl) endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/VisualStudioPrecompiledHeaders.cmake Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,14 @@ +macro(ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS PrecompiledHeaders PrecompiledSource Sources) + get_filename_component(PrecompiledBasename ${PrecompiledHeaders} NAME_WE) + set(PrecompiledBinary "${CMAKE_CURRENT_BINARY_DIR}/${PrecompiledBasename}.pch") + + set_source_files_properties(${PrecompiledSource} + PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeaders}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_OUTPUTS "${PrecompiledBinary}") + + set_source_files_properties(${${Sources}} + PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledHeaders}\" /FI\"${PrecompiledHeaders}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_DEPENDS "${PrecompiledBinary}") + + list(APPEND ${Sources} ${PrecompiledSource}) +endmacro()
--- a/Resources/Configuration.json Wed Apr 16 16:34:09 2014 +0200 +++ b/Resources/Configuration.json Wed Jun 25 12:09:38 2014 +0200 @@ -160,5 +160,14 @@ // will be computed and stored in the Orthanc database. This // information can be used to detect disk corruption, at the price // of a small performance overhead. - "StoreMD5ForAttachments" : true + "StoreMD5ForAttachments" : true, + + // The maximum number of results for a single C-FIND request at the + // Patient, Study or Series level. Setting this option to "0" means + // no limit. + "LimitFindResults" : 0, + + // The maximum number of results for a single C-FIND request at the + // Instance level. Setting this option to "0" means no limit. + "LimitFindInstances" : 0 }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/WebApplications/DrawingDicomizer.js Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,106 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + + +/** + * Parameters of the HTTP server. + **/ + +var orthanc = { + host: 'localhost', + port: 8042 +}; + +var port = 8000; + + + +/** + * The Web application. + **/ + +var http = require('http'); +var querystring = require('querystring'); +var toolbox = require('./NodeToolbox.js'); + +var server = http.createServer(function(req, response) { + switch (req.method) + { + case 'GET': + { + if (req.url == '/') { + toolbox.Redirect('/index.html', response); + } + else if (req.url == '/index.html') { + toolbox.ServeFile('DrawingDicomizer/index.html', response); + } + else if (req.url == '/drawing.js') { + toolbox.ServeFile('DrawingDicomizer/drawing.js', response); + } + else if (req.url == '/orthanc.js') { + toolbox.ServeFile('DrawingDicomizer/orthanc.js', response); + } + else if (req.url == '/jquery.js') { + toolbox.ServeFile('../../../OrthancExplorer/libs/jquery-1.7.2.min.js', response); + } + else if (req.url.startsWith('/orthanc')) { + toolbox.ForwardGetRequest(orthanc, req.url.substr(8), response); + } + else { + toolbox.NotFound(response); + } + + break; + } + + case 'POST': + { + var body = ''; + + req.on('data', function (data) { + body += data; + }); + + req.on('end', function () { + if (req.url == '/orthanc/tools/create-dicom') { + body = JSON.stringify(querystring.parse(body)); + toolbox.ForwardPostRequest(orthanc, '/tools/create-dicom', body, response); + } + else { + toolbox.NotFound(response); + } + }); + + break; + } + + default: + toolbox.NotFound(response); + } +}); + +server.listen(port);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/WebApplications/DrawingDicomizer/drawing.js Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,100 @@ +/** + * Copyright 2010 William Malone (www.williammalone.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + +/** + * This code comes from the blog entry "Create a Drawing App with + * HTML5 Canvas and JavaScript" by William Malone. It is the "simple + * demo" of a pure HTML5 drawing application. + * + * http://www.williammalone.com/articles/create-html5-canvas-javascript-drawing-app/ + * + * To keep this sample code as simple as possible, we do not implement + * hacks for the canvas of Microsoft Internet Explorer. + **/ + + +if ($.browser.msie) { + alert('Please use Mozilla Firefox or Google Chrome. Microsoft Internet Explorer is not supported.'); +} + + +var context; +var clickX = new Array(); +var clickY = new Array(); +var clickDrag = new Array(); +var paint; + + +function addClick(x, y, dragging) +{ + clickX.push(x); + clickY.push(y); + clickDrag.push(dragging); +} + + +function Redraw() +{ + context.fillStyle = '#ffffff'; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas + + context.strokeStyle = '#df4b26'; + context.lineJoin = 'round'; + context.lineWidth = 5; + + for (var i=0; i < clickX.length; i++) { + context.beginPath(); + if (clickDrag[i] && i) { + context.moveTo(clickX[i - 1], clickY[i - 1]); + } else { + context.moveTo(clickX[i] - 1, clickY[i]); + } + context.lineTo(clickX[i], clickY[i]); + context.closePath(); + context.stroke(); + } +} + + +$(document).ready(function() { + context = document.getElementById('canvas').getContext('2d'); + Redraw(); + + $('#canvas').mousedown(function(e) { + var mouseX = e.pageX - this.offsetLeft; + var mouseY = e.pageY - this.offsetTop; + + paint = true; + addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop); + Redraw(); + }); + + $('#canvas').mousemove(function(e) { + if(paint) { + addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true); + Redraw(); + } + }); + + $('#canvas').mouseup(function(e) { + paint = false; + }); + + $('#canvas').mouseleave(function(e) { + paint = false; + }); +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/WebApplications/DrawingDicomizer/index.html Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,24 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>HTML5 Drawing Dicomizer</title> + <style media="screen" type="text/css"> + canvas { + border: 1px inset brown; + } + </style> + <script src="jquery.js"></script> + <script src="drawing.js"></script> + <script src="orthanc.js"></script> + </head> + <body> + <canvas id="canvas" width="490" height="220"></canvas> + <p> + Patient Name: <input type="text" id="patientName"></input> + </p> + <p> + <button id="submit">Submit</button> + </p> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,47 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +$(document).ready(function() { + $('#submit').click(function(event) { + var png = context.canvas.toDataURL(); + + $.ajax({ + type: 'POST', + url: '/orthanc/tools/create-dicom', + data: { + PatientName: $('#patientName').val(), + PixelData: png, + Modality: 'RX' + } + }) + .success(function( msg ) { + alert('Your drawing has been dicomized!\n\n' + msg); + }); + + return false; + }); +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/WebApplications/NodeToolbox.js Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,107 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ + + +var fs = require('fs'); +var http = require('http'); + + +function ForwardGetRequest(orthanc, path, res) { + var opts = orthanc; + opts.path = path; + opts.method = 'GET'; + + http.get(opts, function(response) { + response.setEncoding('utf-8'); + response.on('data', function(chunk) { + res.write(chunk); + }); + response.on('end', function() { + res.end(); + }); + }).on('error', function(e) { + console.log('Got error on GET forwarding: ' + e.message + ' (' + path + ')'); + }); +} + + +function ForwardPostRequest(orthanc, path, body, res) { + var opts = orthanc; + opts.path = path; + opts.method = 'POST'; + opts.headers = { + 'Content-Length': body.length + } + + var req = http.request(opts, function(response) { + response.setEncoding('utf-8'); + response.on('data', function(chunk) { + res.write(chunk); + }); + response.on('end', function() { + res.end(); + }); + }).on('error', function(e) { + console.log('Got error on POST forwarding: ' + e.message + ' (' + path + ')'); + }); + + req.write(body); + req.end(); +} + + +function ServeFile(filename, res) { + fs.readFile(filename, function(r, c) { + res.end(c.toString()); + }); +} + + +function NotFound(res) { + res.writeHead(404, {'Content-Type': 'text/plain'}); + res.end(); +} + + +function Redirect(path, res) { + res.writeHead(301, { + 'Content-Type': 'text/plain', + 'Location': path + }); + res.end(); +} + + +String.prototype.startsWith = function(prefix) { + return this.indexOf(prefix) === 0; +} + + +module.exports.ForwardGetRequest = ForwardGetRequest; +module.exports.ForwardPostRequest = ForwardPostRequest; +module.exports.NotFound = NotFound; +module.exports.Redirect = Redirect; +module.exports.ServeFile = ServeFile;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/WebApplications/README.txt Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,34 @@ +=================== +GENERAL INFORMATION +=================== + +This folder contains sample Web applications. + +These Web applications make use of NodeJs (http://nodejs.org/). To run +the applications, you therefore need to install NodeJs on your +computer. NodeJs acts here as a lightweight, cross-platform Web server +that statically serves the HTML/JavaScript files and that dynamically +serves the Orthanc REST API as a reverse proxy (to avoid cross-domain +problems with AJAX). + +Once NodeJs is installed, start Orthanc with default parameters +(i.e. HTTP port set to 8042), start NodeJs with the sample application +you are interested in (e.g. "node DrawingDicomizer.js"). Then, open +http://localhost:8000/ with a standard Web browser to try the sample +application. + + + +======================================= +DRAWING DICOMIZER (DrawingDicomizer.js) +======================================= + +This sample shows how to convert the content of a HTML5 canvas as a +DICOM file, using a single AJAX request to Orthanc. + +Internally, the content of the HTML5 canvas is serialized through the +standard "toDataURL()" method of the canvas object. This returns a +string containing the PNG image encoded using the Data URI Scheme +(http://en.wikipedia.org/wiki/Data_URI_scheme). Such a string is then +sent to Orthanc using the '/tools/create-dicom' REST call, that +transparently decompresses the PNG image into a DICOM image.
--- a/UnitTestsSources/DicomMap.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/DicomMap.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include "../Core/Uuid.h"
--- a/UnitTestsSources/FileStorage.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/FileStorage.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include <ctype.h> @@ -27,7 +60,7 @@ TEST(FileStorage, Basic) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); std::string data = Toolbox::GenerateUuid(); std::string uid = s.Create(data); @@ -40,7 +73,7 @@ TEST(FileStorage, Basic2) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); std::vector<uint8_t> data; StringToVector(data, Toolbox::GenerateUuid()); @@ -54,7 +87,7 @@ TEST(FileStorage, EndToEnd) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); s.Clear(); std::list<std::string> u; @@ -87,7 +120,7 @@ TEST(FileStorageAccessor, Simple) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); FileStorageAccessor accessor(s); std::string data = "Hello world"; @@ -106,7 +139,7 @@ TEST(FileStorageAccessor, NoCompression) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); CompressedFileStorageAccessor accessor(s); accessor.SetCompressionForNextOperations(CompressionType_None); @@ -126,7 +159,7 @@ TEST(FileStorageAccessor, NoCompression2) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); CompressedFileStorageAccessor accessor(s); accessor.SetCompressionForNextOperations(CompressionType_None); @@ -147,7 +180,7 @@ TEST(FileStorageAccessor, Compression) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); CompressedFileStorageAccessor accessor(s); accessor.SetCompressionForNextOperations(CompressionType_Zlib); @@ -166,7 +199,7 @@ TEST(FileStorageAccessor, Mix) { - FileStorage s("FileStorageUnitTests"); + FileStorage s("UnitTestsStorage"); CompressedFileStorageAccessor accessor(s); std::string r;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/FromDcmtk.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,147 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../OrthancServer/FromDcmtkBridge.h" +#include "../OrthancServer/OrthancInitialization.h" +#include "../OrthancServer/DicomModification.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngReader.h" +#include "../Core/ImageFormats/PngWriter.h" + +using namespace Orthanc; + +TEST(DicomFormat, Tag) +{ + ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); + + DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); + ASSERT_EQ(0x0008, t.GetGroup()); + ASSERT_EQ(0x103E, t.GetElement()); + + t = FromDcmtkBridge::ParseTag("0020-e040"); + ASSERT_EQ(0x0020, t.GetGroup()); + ASSERT_EQ(0xe040, t.GetElement()); + + // Test ==() and !=() operators + ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); + ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); +} + + +TEST(DicomModification, Basic) +{ + DicomModification m; + m.SetupAnonymization(); + //m.SetLevel(DicomRootLevel_Study); + //m.Replace(DICOM_TAG_PATIENT_ID, "coucou"); + //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); + + ParsedDicomFile o; + 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()); + if (i > 4) + o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); + m.Apply(*f); + f->SaveToFile(b); + } +} + + +#include <dcmtk/dcmdata/dcuid.h> + +TEST(DicomModification, Png) +{ + // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image) + std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; + + std::string m, c; + Toolbox::DecodeDataUriScheme(m, c, s); + + ASSERT_EQ("image/png", m); + ASSERT_EQ(116, c.size()); + + std::string cc; + Toolbox::DecodeBase64(cc, c); + PngReader reader; + reader.ReadFromMemory(cc); + + ASSERT_EQ(5, reader.GetHeight()); + ASSERT_EQ(5, reader.GetWidth()); + ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); + + ParsedDicomFile o; + o.EmbedImage(s); + o.SaveToFile("UnitTestsResults/png1.dcm"); + + // Red dot, without alpha channel + s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII="; + o.EmbedImage(s); + o.SaveToFile("UnitTestsResults/png2.dcm"); + + // Check box in Graylevel8 + s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="; + o.EmbedImage(s); + //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); + o.SaveToFile("UnitTestsResults/png3.dcm"); + + + { + // Gradient in Graylevel16 + + ImageBuffer img; + img.SetWidth(256); + img.SetHeight(256); + img.SetFormat(PixelFormat_Grayscale16); + + int v = 0; + for (unsigned int y = 0; y < img.GetHeight(); y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y)); + for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++) + { + *p = v; + } + } + + o.EmbedImage(img.GetAccessor()); + o.SaveToFile("UnitTestsResults/png4.dcm"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ImageProcessingTests.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,78 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/DicomFormat/DicomImageInformation.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/ImageProcessing.h" + +using namespace Orthanc; + + +TEST(DicomImageInformation, ExtractPixelFormat1) +{ + // 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"); + + DicomImageInformation info(m); + PixelFormat format; + ASSERT_TRUE(info.ExtractPixelFormat(format)); + ASSERT_EQ(PixelFormat_Grayscale16, format); +} + + +TEST(DicomImageInformation, ExtractPixelFormat2) +{ + // 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"); + + DicomImageInformation info(m); + PixelFormat format; + ASSERT_TRUE(info.ExtractPixelFormat(format)); + ASSERT_EQ(PixelFormat_SignedGrayscale16, format); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/JpegLossless.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../OrthancServer/Internals/DicomImageDecoder.h" + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + +#include <dcmtk/dcmdata/dcfilefo.h> + +#include "../OrthancServer/ParsedDicomFile.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngWriter.h" + +using namespace Orthanc; + + + +// TODO Write a test + + +#endif
--- a/UnitTestsSources/Lua.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/Lua.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include "../Core/Lua/LuaFunctionCall.h"
--- a/UnitTestsSources/MemoryCache.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/MemoryCache.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include <glog/logging.h>
--- a/UnitTestsSources/MultiThreading.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/MultiThreading.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,8 +1,46 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include <glog/logging.h> + #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" #include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/Locker.h" +#include "../Core/MultiThreading/Mutex.h" +#include "../Core/MultiThreading/ReaderWriterLock.h" #include "../Core/MultiThreading/ThreadedCommandProcessor.h" using namespace Orthanc; @@ -185,3 +223,54 @@ ASSERT_TRUE(s.find(i) != s.end()); } } + + +TEST(MultiThreading, Mutex) +{ + Mutex mutex; + Locker locker(mutex); +} + + +TEST(MultiThreading, ReaderWriterLock) +{ + ReaderWriterLock lock; + + { + Locker locker1(lock.ForReader()); + Locker locker2(lock.ForReader()); + } + + { + Locker locker3(lock.ForWriter()); + } +} + + + + + +#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" + +TEST(ReusableDicomUserConnection, DISABLED_Basic) +{ + ReusableDicomUserConnection c; + c.SetMillisecondsBeforeClose(200); + printf("START\n"); fflush(stdout); + { + ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); + } + + printf("**\n"); fflush(stdout); + Toolbox::USleep(1000000); + printf("**\n"); fflush(stdout); + + { + ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); + } + + Toolbox::ServerBarrier(); + printf("DONE\n"); fflush(stdout); +}
--- a/UnitTestsSources/Png.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/Png.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,8 +1,41 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include <stdint.h> -#include "../Core/FileFormats/PngReader.h" -#include "../Core/FileFormats/PngWriter.h" +#include "../Core/ImageFormats/PngReader.h" +#include "../Core/ImageFormats/PngWriter.h" #include "../Core/Toolbox.h" #include "../Core/Uuid.h" @@ -26,10 +59,10 @@ } } - w.WriteToFile("ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); + w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "ColorPattern.png"); + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); Orthanc::Toolbox::ComputeMD5(md5, f); ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); } @@ -51,10 +84,10 @@ } } - w.WriteToFile("Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); + w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "Gray8Pattern.png"); + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); Orthanc::Toolbox::ComputeMD5(md5, f); ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); } @@ -78,10 +111,10 @@ } } - w.WriteToFile("Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "Gray16Pattern.png"); + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); Orthanc::Toolbox::ComputeMD5(md5, f); ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); } @@ -119,8 +152,8 @@ v = 0; for (int y = 0; y < height; y++) { - uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r.GetBuffer() + y * r.GetPitch()); - ASSERT_EQ(p, r.GetBuffer(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 (int x = 0; x < width; x++, p++, v++) { ASSERT_EQ(*p, v); @@ -142,8 +175,8 @@ v = 0; for (int y = 0; y < height; y++) { - uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r2.GetBuffer() + y * r2.GetPitch()); - ASSERT_EQ(p, r2.GetBuffer(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 (int x = 0; x < width; x++, p++, v++) { ASSERT_EQ(*p, v);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/PrecompiledHeadersUnitTests.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,33 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/PrecompiledHeadersUnitTests.h Wed Jun 25 12:09:38 2014 +0200 @@ -0,0 +1,40 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../OrthancServer/PrecompiledHeadersServer.h" + +#if ORTHANC_USE_PRECOMPILED_HEADERS == 1 +#include "../Core/EnumerationDictionary.h" +#include <gtest/gtest.h> +#endif
--- a/UnitTestsSources/RestApi.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/RestApi.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include <ctype.h>
--- a/UnitTestsSources/SQLite.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/SQLite.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include "../Core/Toolbox.h" @@ -18,9 +51,9 @@ TEST(SQLite, Connection) { - Toolbox::RemoveFile("coucou"); + Toolbox::RemoveFile("UnitTestsResults/coucou"); SQLite::Connection c; - c.Open("coucou"); + c.Open("UnitTestsResults/coucou"); c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)"); c.Execute("INSERT INTO c VALUES(NULL, 42);"); } @@ -283,7 +316,7 @@ ASSERT_EQ(42ll, s.ColumnInt64(1)); ASSERT_TRUE(s.Step()); ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1)); - ASSERT_FLOAT_EQ(42.5, s.ColumnDouble(1)); + ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1)); ASSERT_TRUE(s.Step()); ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1)); ASSERT_EQ("Hello", s.ColumnString(1));
--- a/UnitTestsSources/SQLiteChromium.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/SQLiteChromium.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include "../Core/Toolbox.h"
--- a/UnitTestsSources/ServerIndexTests.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include "../OrthancServer/DatabaseWrapper.h" @@ -523,7 +556,7 @@ TEST(ServerIndex, AttachmentRecycling) { - const std::string path = "OrthancStorageUnitTests"; + const std::string path = "UnitTestsStorage"; Toolbox::RemoveFile(path + "/index"); ServerContext context(path, ":memory:"); // The SQLite DB is in memory ServerIndex& index = context.GetIndex();
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "../Core/EnumerationDictionary.h" #include "gtest/gtest.h" @@ -10,7 +43,6 @@ #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" #include "../Core/Uuid.h" -#include "../OrthancServer/FromDcmtkBridge.h" #include "../OrthancServer/OrthancInitialization.h" using namespace Orthanc; @@ -178,24 +210,6 @@ ASSERT_EQ(a["aaa"], ""); } -TEST(DicomFormat, Tag) -{ - ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); - - DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); - ASSERT_EQ(0x0008, t.GetGroup()); - ASSERT_EQ(0x103E, t.GetElement()); - - t = FromDcmtkBridge::ParseTag("0020-e040"); - ASSERT_EQ(0x0020, t.GetGroup()); - ASSERT_EQ(0xe040, t.GetElement()); - - // Test ==() and !=() operators - ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); - ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); -} - - TEST(Uri, SplitUriComponents) { UriComponents c; @@ -325,14 +339,25 @@ } +static std::string EncodeBase64Bis(const std::string& s) +{ + std::string result; + Toolbox::EncodeBase64(result, s); + return result; +} + + TEST(Toolbox, Base64) { - ASSERT_EQ("", Toolbox::EncodeBase64("")); - ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a")); + ASSERT_EQ("", EncodeBase64Bis("")); + ASSERT_EQ("YQ==", EncodeBase64Bis("a")); const std::string hello = "SGVsbG8gd29ybGQ="; - ASSERT_EQ(hello, Toolbox::EncodeBase64("Hello world")); - ASSERT_EQ("Hello world", Toolbox::DecodeBase64(hello)); + ASSERT_EQ(hello, EncodeBase64Bis("Hello world")); + + std::string decoded; + Toolbox::DecodeBase64(decoded, hello); + ASSERT_EQ("Hello world", decoded); } TEST(Toolbox, PathToExecutable) @@ -432,8 +457,8 @@ #if defined(__linux) TEST(OrthancInitialization, AbsoluteDirectory) { - ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello")); - ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp")); + ASSERT_EQ("/tmp/hello", Configuration::InterpretRelativePath("/tmp", "hello")); + ASSERT_EQ("/tmp", Configuration::InterpretRelativePath("/tmp", "/tmp")); } #endif @@ -570,7 +595,10 @@ #if defined(_WIN32) ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); -#elif defined(__linux) +#elif defined(__APPLE__) + ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); + +#elif defined(__linux) || defined(__FreeBSD_kernel__) #if !defined(__BYTE_ORDER) # error Support your platform here @@ -588,7 +616,6 @@ } - int main(int argc, char **argv) { // Initialize Google's logging library. @@ -602,6 +629,8 @@ google::InitGoogleLogging("Orthanc"); + Toolbox::CreateDirectory("UnitTestsResults"); + OrthancInitialize(); ::testing::InitGoogleTest(&argc, argv); int result = RUN_ALL_TESTS();
--- a/UnitTestsSources/Versions.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/Versions.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include <stdint.h>
--- a/UnitTestsSources/Zip.cpp Wed Apr 16 16:34:09 2014 +0200 +++ b/UnitTestsSources/Zip.cpp Wed Jun 25 12:09:38 2014 +0200 @@ -1,3 +1,36 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" #include "../Core/OrthancException.h" @@ -11,7 +44,7 @@ TEST(ZipWriter, Basic) { Orthanc::ZipWriter w; - w.SetOutputPath("hello.zip"); + w.SetOutputPath("UnitTestsResults/hello.zip"); w.Open(); w.OpenFile("world/hello"); w.Write("Hello world"); @@ -21,7 +54,7 @@ TEST(ZipWriter, Basic64) { Orthanc::ZipWriter w; - w.SetOutputPath("hello64.zip"); + w.SetOutputPath("UnitTestsResults/hello64.zip"); w.SetZip64(true); w.Open(); w.OpenFile("world/hello"); @@ -33,7 +66,7 @@ { Orthanc::ZipWriter w; ASSERT_THROW(w.Open(), Orthanc::OrthancException); - w.SetOutputPath("hello3.zip"); + w.SetOutputPath("UnitTestsResults/hello3.zip"); w.Open(); ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException); } @@ -91,7 +124,7 @@ { static const std::string SPACES = " "; - HierarchicalZipWriter w("hello2.zip"); + HierarchicalZipWriter w("UnitTestsResults/hello2.zip"); w.SetCompressionLevel(0);