changeset 930:27d256e0b458 mac

integration mainline -> mac
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 24 Jun 2014 16:47:18 +0200
parents 882833632b1f (current diff) 401a8273fd71 (diff)
children 29174c7b4631
files CMakeLists.txt Core/FileFormats/PngReader.cpp Core/FileFormats/PngReader.h Core/FileFormats/PngWriter.cpp Core/FileFormats/PngWriter.h Core/Toolbox.cpp OrthancServer/DicomProtocol/DicomUserConnection.cpp Resources/CMake/BoostConfiguration.cmake Resources/CMake/Compiler.cmake Resources/CMake/DcmtkConfiguration.cmake Resources/CMake/GoogleLogConfiguration.cmake Resources/CMake/LibCurlConfiguration.cmake UnitTestsSources/UnitTestsMain.cpp
diffstat 164 files changed, 9356 insertions(+), 3248 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Apr 16 17:02:07 2014 +0200
+++ b/CMakeLists.txt	Tue Jun 24 16:47:18 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"
       )
--- a/Core/Cache/MemoryCache.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/Cache/MemoryCache.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/ChunkedBuffer.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/ChunkedBuffer.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/Compression/BufferCompressor.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "BufferCompressor.h"
 
 namespace Orthanc
--- a/Core/Compression/HierarchicalZipWriter.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "HierarchicalZipWriter.h"
 
 #include "../Toolbox.h"
--- a/Core/Compression/ZipWriter.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/Compression/ZipWriter.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/Compression/ZlibCompressor.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "ZlibCompressor.h"
 
 #include <stdio.h>
--- a/Core/DicomFormat/DicomArray.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/DicomFormat/DicomArray.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "DicomMap.h"
 
 #include <stdio.h>
--- a/Core/DicomFormat/DicomTag.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/DicomFormat/DicomTag.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "DicomTag.h"
 
 #include "../OrthancException.h"
--- a/Core/DicomFormat/DicomTag.h	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/DicomFormat/DicomTag.h	Tue Jun 24 16:47:18 2014 +0200
@@ -115,4 +115,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 17:02:07 2014 +0200
+++ b/Core/Enumerations.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/Enumerations.h	Tue Jun 24 16:47:18 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 17:02:07 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 17:02:07 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 17:02:07 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 17:02:07 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 17:02:07 2014 +0200
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "CompressedFileStorageAccessor.h"
 
 #include "../OrthancException.h"
--- a/Core/FileStorage/FileStorage.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/FileStorage/FileStorage.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "FileStorageAccessor.h"
 
 namespace Orthanc
--- a/Core/FileStorage/StorageAccessor.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/FileStorage/StorageAccessor.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "StorageAccessor.h"
 
 namespace Orthanc
--- a/Core/HttpClient.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/HttpClient.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "PrecompiledHeaders.h"
 #include "HttpClient.h"
 
 #include "../Core/Toolbox.h"
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "EmbeddedResourceHttpHandler.h"
 
 #include "../OrthancException.h"
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "FilesystemHttpHandler.h"
 
 #include "../OrthancException.h"
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/HttpServer/HttpFileSender.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/HttpServer/HttpHandler.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "HttpHandler.h"
 
 #include <string.h>
--- a/Core/HttpServer/HttpOutput.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "HttpOutput.h"
 
 #include <iostream>
--- a/Core/HttpServer/MongooseServer.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/Lua/LuaContext.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "LuaContext.h"
 
 #include <glog/logging.h>
--- a/Core/Lua/LuaFunctionCall.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/Lua/LuaFunctionCall.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "LuaFunctionCall.h"
 
 
--- a/Core/MultiThreading/ArrayFilledByThreads.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/MultiThreading/ArrayFilledByThreads.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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__)
+#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__)
+
+  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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 ReaderWriterLock
+  {
+  private:
+    struct PImpl;
+
+    PImpl *pimpl_;
+
+  public:
+    ReaderWriterLock();
+
+    virtual ~ReaderWriterLock();
+
+    ILockable& ForReader();
+
+    ILockable& ForWriter();
+  };
+}
--- a/Core/MultiThreading/SharedMessageQueue.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/MultiThreading/SharedMessageQueue.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "SharedMessageQueue.h"
 
 namespace Orthanc
--- a/Core/MultiThreading/ThreadedCommandProcessor.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "ThreadedCommandProcessor.h"
 
 #include "../OrthancException.h"
--- a/Core/OrthancException.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/OrthancException.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/RestApi/RestApi.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/RestApi/RestApiOutput.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/RestApi/RestApiPath.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "RestApiPath.h"
 
 #include <cassert>
--- a/Core/SQLite/Connection.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/SQLite/Connection.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -34,6 +34,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "Connection.h"
 
 #include <memory>
--- a/Core/SQLite/FunctionContext.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/SQLite/FunctionContext.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -31,6 +31,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "FunctionContext.h"
 
 #include <sqlite3.h>
--- a/Core/SQLite/Statement.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/SQLite/Statement.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -34,6 +34,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "Statement.h"
 
 #include "Connection.h"
--- a/Core/SQLite/StatementId.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/SQLite/StatementId.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -34,6 +34,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "StatementId.h"
 
 #include <string.h>
--- a/Core/SQLite/StatementReference.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/SQLite/StatementReference.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -34,6 +34,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "StatementReference.h"
 
 #include "../OrthancException.h"
--- a/Core/SQLite/Transaction.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/SQLite/Transaction.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -34,6 +34,7 @@
  **/
 
 
+#include "../PrecompiledHeaders.h"
 #include "Transaction.h"
 
 namespace Orthanc
--- a/Core/Toolbox.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/Core/Toolbox.cpp	Tue Jun 24 16:47:18 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) || defined(__APPLE__)
+#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
   }
 
 
@@ -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 17:02:07 2014 +0200
+++ b/Core/Toolbox.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Core/Uuid.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "PrecompiledHeaders.h"
 #include "Uuid.h"
 
 // http://stackoverflow.com/a/1626302
--- a/INSTALL	Wed Apr 16 17:02:07 2014 +0200
+++ b/INSTALL	Tue Jun 24 16:47:18 2014 +0200
@@ -71,7 +71,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 17:02:07 2014 +0200
+++ b/LinuxCompilation.txt	Tue Jun 24 16:47:18 2014 +0200
@@ -73,6 +73,8 @@
 	-DUSE_SYSTEM_DCMTK=OFF \
 	-DUSE_SYSTEM_MONGOOSE=OFF \
 	-DUSE_SYSTEM_JSONCPP=OFF \
+        -DENABLE_JPEG=OFF \
+        -DENABLE_JPEG_LOSSLESS=OFF \
 	~/Orthanc 
 
 
@@ -89,6 +91,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 +108,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 +130,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 +142,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 +179,8 @@
         -DALLOW_DOWNLOADS=ON \
 	-DUSE_SYSTEM_MONGOOSE=OFF \
         -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \
+        -DENABLE_JPEG=OFF \
+        -DENABLE_JPEG_LOSSLESS=OFF \
 	~/Orthanc
 
 
@@ -169,8 +193,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 17:02:07 2014 +0200
+++ b/NEWS	Tue Jun 24 16:47:18 2014 +0200
@@ -1,6 +1,28 @@
 Pending changes in the mainline
 ===============================
 
+* 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/Instance.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/Instance.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/OrthancConnection.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/Patient.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../Core/PrecompiledHeaders.h"
 #include "Patient.h"
 
 #include "OrthancConnection.h"
--- a/OrthancCppClient/Series.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancCppClient/Series.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/Product.json	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancCppClient/Study.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../Core/PrecompiledHeaders.h"
 #include "Study.h"
 
 #include "OrthancConnection.h"
--- a/OrthancServer/DatabaseWrapper.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeadersServer.h"
 #include "DicomFindAnswers.h"
 
 #include "../FromDcmtkBridge.h"
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Jun 24 16:47:18 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"
@@ -59,12 +108,28 @@
 
 
 #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
@@ -108,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 
@@ -165,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);
     }
   }
 
@@ -197,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);
@@ -206,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)
       {
@@ -217,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();
     }
 
@@ -237,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
@@ -293,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
@@ -506,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),
@@ -519,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()
@@ -526,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)
@@ -599,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));
 
@@ -612,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));
 
@@ -747,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 17:02:07 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Tue Jun 24 16:47:18 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();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 2014 +0200
@@ -0,0 +1,684 @@
+/**
+ * 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
+{
+  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 (DicomImageDecoder::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_;
+    }
+  };
+
+
+  static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a);
+  static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011);
+
+  bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset)
+  {
+    DcmElement* e;
+    char* c;
+
+    // Check whether the DICOM instance contains an image encoded with
+    // the PMSCT_RLE1 scheme.
+    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() ||
+        e == NULL ||
+        !e->isaString() ||
+        !e->getString(c).good() ||
+        c == NULL ||
+        strcmp("PMSCT_RLE1", c))
+    {
+      return false;
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+
+  bool DicomImageDecoder::DecodePsmctRle1(std::string& output,
+                                          DcmDataset& dataset)
+  {
+    // Check whether the DICOM instance contains an image encoded with
+    // the PMSCT_RLE1 scheme.
+    if (!IsPsmctRle1(dataset))
+    {
+      return false;
+    }
+
+    // OK, this is a custom RLE encoding from Philips. Get the pixel
+    // data from the appropriate private DICOM tag.
+    Uint8* pixData = NULL;
+    DcmElement* e;
+    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() ||
+        e == NULL ||
+        e->getUint8Array(pixData) != EC_Normal)
+    {
+      return false;
+    }    
+
+    // The "unsigned" below IS VERY IMPORTANT
+    const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData);
+    const size_t length = e->getLength();
+
+    /**
+     * The code below is an adaptation of a sample code for GDCM by
+     * Mathieu Malaterre (under a BSD license).
+     * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html
+     **/
+
+    // RLE pass
+    std::vector<uint8_t> temp;
+    temp.reserve(length);
+    for (size_t i = 0; i < length; i++)
+    {
+      if (inbuffer[i] == 0xa5)
+      {
+        temp.push_back(inbuffer[i+2]);
+        for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--)
+        {
+          temp.push_back(inbuffer[i+2]);
+        }
+        i += 2;
+      }
+      else
+      {
+        temp.push_back(inbuffer[i]);
+      }
+    }
+
+    // Delta encoding pass
+    uint16_t delta = 0;
+    output.clear();
+    output.reserve(temp.size());
+    for (size_t i = 0; i < temp.size(); i++)
+    {
+      uint16_t value;
+
+      if (temp[i] == 0x5a)
+      {
+        uint16_t v1 = temp[i + 1];
+        uint16_t v2 = temp[i + 2];
+        value = (v2 << 8) + v1;
+        i += 2;
+      }
+      else
+      {
+        value = delta + (int8_t) temp[i];
+      }
+
+      output.push_back(value & 0xff);
+      output.push_back(value >> 8);
+      delta = value;
+    }
+
+    if (output.size() % 2)
+    {
+      output.resize(output.size() - 1);
+    }
+
+    return true;
+  }
+
+
+  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::IsJpegLossless(const DcmDataset& dataset)
+  {
+    // http://support.dcmtk.org/docs/dcxfer_8h-source.html
+    return (dataset.getOriginalXfer() == EXS_JPEGLSLossless ||
+            dataset.getOriginalXfer() == EXS_JPEGLSLossy);
+  }
+
+
+  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, &parameters, 
+                                        &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	Tue Jun 24 16:47:18 2014 +0200
@@ -0,0 +1,86 @@
+/**
+ * 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 DecodePsmctRle1(std::string& output,
+                                DcmDataset& dataset);
+
+    static bool IsUncompressedImage(const DcmDataset& dataset);
+
+    static bool IsJpegLossless(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 17:02:07 2014 +0200
+++ b/OrthancServer/Internals/FindScp.cpp	Tue Jun 24 16:47:18 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"
--- a/OrthancServer/Internals/MoveScp.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancServer/Internals/MoveScp.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Tue Jun 24 16:47:18 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
 {
@@ -450,13 +453,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();
     }
 
 
--- a/OrthancServer/OrthancInitialization.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Tue Jun 24 16:47:18 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,42 +286,14 @@
 
     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)
     {
@@ -304,10 +305,8 @@
 
 
 
-  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,34 +324,7 @@
         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)
     {
@@ -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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancInitialization.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "../PrecompiledHeadersServer.h"
 #include "OrthancRestApi.h"
 
 #include <glog/logging.h>
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/ServerContext.cpp	Tue Jun 24 16:47:18 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)
@@ -267,10 +292,10 @@
 
 
   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 17:02:07 2014 +0200
+++ b/OrthancServer/ServerContext.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/ServerEnumerations.h	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/ServerIndex.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/OrthancServer/ServerToolbox.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "PrecompiledHeadersServer.h"
 #include "ServerToolbox.h"
 
 #include "../Core/OrthancException.h"
--- a/OrthancServer/ToDcmtkBridge.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancServer/ToDcmtkBridge.cpp	Tue Jun 24 16:47:18 2014 +0200
@@ -30,6 +30,7 @@
  **/
 
 
+#include "PrecompiledHeadersServer.h"
 #include "ToDcmtkBridge.h"
 
 #include <memory>
--- a/OrthancServer/main.cpp	Wed Apr 16 17:02:07 2014 +0200
+++ b/OrthancServer/main.cpp	Tue Jun 24 16:47:18 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"
@@ -130,7 +131,7 @@
       return true;
     }
 
-    if (!IsKnownAETitle(callingAet))
+    if (!Configuration::IsKnownAETitle(callingAet))
     {
       LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << callingAet << "\"";
       return false;
@@ -324,24 +325,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 +352,7 @@
 
     try
     {
-      context.GetIndex().SetMaximumPatientCount(GetGlobalIntegerParameter("MaximumPatientCount", 0));
+      context.GetIndex().SetMaximumPatientCount(Configuration::GetGlobalIntegerParameter("MaximumPatientCount", 0));
     }
     catch (...)
     {
@@ -360,7 +361,7 @@
 
     try
     {
-      uint64_t size = GetGlobalIntegerParameter("MaximumStorageSize", 0);
+      uint64_t size = Configuration::GetGlobalIntegerParameter("MaximumStorageSize", 0);
       context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024);
     }
     catch (...)
@@ -374,28 +375,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 +414,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 +424,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 17:02:07 2014 +0200
+++ b/Resources/CMake/BoostConfiguration.cmake	Tue Jun 24 16:47:18 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")
@@ -55,7 +55,8 @@
   set(BOOST_SOURCES)
 
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+      ${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
@@ -129,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 17:02:07 2014 +0200
+++ b/Resources/CMake/Compiler.cmake	Tue Jun 24 16:47:18 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")
--- a/Resources/CMake/DcmtkConfiguration.cmake	Wed Apr 16 17:02:07 2014 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Tue Jun 24 16:47:18 2014 +0200
@@ -48,10 +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" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+      ${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 17:02:07 2014 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.cmake	Tue Jun 24 16:47:18 2014 +0200
@@ -29,7 +29,8 @@
   set(ac_google_end_namespace "}")
 
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+      ${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)
@@ -85,7 +86,8 @@
   endif()
 
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+      ${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(
@@ -138,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)
--- a/Resources/CMake/LibCurlConfiguration.cmake	Wed Apr 16 17:02:07 2014 +0200
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Tue Jun 24 16:47:18 2014 +0200
@@ -41,7 +41,8 @@
   endif()
 
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+      ${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 17:02:07 2014 +0200
+++ b/Resources/CMake/LuaConfiguration.cmake	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/Resources/CMake/MongooseConfiguration.cmake	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/WebApplications/DrawingDicomizer.js	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/DicomMap.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/FileStorage.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/Lua.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/MemoryCache.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/MultiThreading.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/Png.cpp	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/RestApi.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/SQLite.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/SQLiteChromium.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/ServerIndexTests.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue Jun 24 16:47:18 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
 
@@ -573,7 +598,7 @@
 #elif defined(__APPLE__)
   ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness());
 
-#elif defined(__linux)
+#elif defined(__linux) || defined(__FreeBSD_kernel__)
 
 #if !defined(__BYTE_ORDER)
 #  error Support your platform here
@@ -591,7 +616,6 @@
 }
 
 
-
 int main(int argc, char **argv)
 {
   // Initialize Google's logging library.
@@ -605,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 17:02:07 2014 +0200
+++ b/UnitTestsSources/Versions.cpp	Tue Jun 24 16:47:18 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 17:02:07 2014 +0200
+++ b/UnitTestsSources/Zip.cpp	Tue Jun 24 16:47:18 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);